From 4ed25ac361f402a8c36f84e605b9347bf00eee60 Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Tue, 12 Apr 2016 23:25:11 +0200 Subject: [PATCH] Emesary mp support Add support for transfer string Change Type to NotificationType as Type can be confusing --- Aircraft/Generic/an_spn_46.nas | 2 +- Nasal/emesary.nas | 41 +++++++++- Nasal/emesary_mp_bridge.nas | 144 ++++++++++++++++++++++----------- 3 files changed, 135 insertions(+), 52 deletions(-) diff --git a/Aircraft/Generic/an_spn_46.nas b/Aircraft/Generic/an_spn_46.nas index 9240b8b09..b7d210d47 100644 --- a/Aircraft/Generic/an_spn_46.nas +++ b/Aircraft/Generic/an_spn_46.nas @@ -275,7 +275,7 @@ var ANSPN46_System = new_class.Receive = func(notification) { - if (notification.Type == "ANSPN46ActiveResponseNotification") + if (notification.NotificationType == "ANSPN46ActiveResponseNotification") { if (notification.Tuned) { diff --git a/Nasal/emesary.nas b/Nasal/emesary.nas index dc065e7c4..0e5afcc4c 100644 --- a/Nasal/emesary.nas +++ b/Nasal/emesary.nas @@ -153,7 +153,7 @@ var Transmitter = # SubClasses can add extra properties or methods. # Properties: # Ident : Generic message identity. Can be an ident, or for simple messages a value that needs transmitting. -# Type : Message Type +# NotificationType : Notification Type # IsDistinct : non zero if this message supercedes previous messages of this type. # Distinct messages are usually sent often and self contained # (i.e. no relative state changes such as toggle value) @@ -168,7 +168,7 @@ var Notification = { var new_class = { parents: [Notification]}; new_class.Ident = _ident; - new_class.Type = _type; + new_class.NotificationType = _type; new_class.IsDistinct = 1; new_class.FromIncomingBridge = 0; new_class.Callsign = nil; @@ -221,6 +221,43 @@ var TransferCoord = return mp_broadcast.Binary.decodeCoord(v); } }; +var TransferString = +{ +# +# just to pack a valid range and keep the lower and very upper control codes for seperators +# that way we don't need to do anything special to encode the string. + getalphanumericchar : func(v) + { + if (find(v,"-./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_abcdefghijklmnopqrstuvwxyz") > 0) + return v; + return nil; + }, + encode : func(v) + { + var l = size(v); + if (l > 16) + l = 16; + var rv = mp_broadcast.Binary.encodeByte(l); + +print ("Encode string ",v," l=",l); + for(var ii = 0; ii < l; ii = ii + 1) + { +printf("%d,%d (%s)\n",ii+1,1,rv); + ev = TransferString.getalphanumericchar(substr(v,ii,1)); + if (ev != nil) + rv = rv ~ ev; + } + print("String encode l=",l," val=",rv); + return rv; + }, + decode : func(v) + { + var l = mp_broadcast.Binary.decodeByte(v); + var rv = substr(v,1,l-1); + print("String decode l=",l," val=",rv); + return rv; + } +}; var TransferByte = { diff --git a/Nasal/emesary_mp_bridge.nas b/Nasal/emesary_mp_bridge.nas index e8d0f3e8a..c1dc58293 100644 --- a/Nasal/emesary_mp_bridge.nas +++ b/Nasal/emesary_mp_bridge.nas @@ -1,28 +1,69 @@ + #--------------------------------------------------------------------------- + # + # Title : EMESARY multiplayer bridge + # + # File Type : Implementation File + # + # Description : Bridges selected emesary notifications over MP + # : To send a message use a Transmitter with an object. That's all there is to it. + # + # References : http://chateau-logic.com/content/emesary-nasal-implementation-flightgear + # + # Author : Richard Harrison (richard@zaretto.com) + # + # Creation Date : 04 April 2016 + # + # Version : 4.8 + # + # Copyright © 2016 Richard Harrison Released under GPL V2 + # + #---------------------------------------------------------------------------*/ + +# Example of connecting an incoming and outgoing bridge (should reside inside an aircraft nasal file) +# +# var routedNotifications = [notifications.TacticalNotification.new(nil)]; +# var incomingBridge = emesary_mp_bridge.IncomingMPBridge.startMPBridge(routedNotifications); +# var outgoingBridge = emesary_mp_bridge.OutgoingMPBridge.new("F-15mp",routedNotifications); +#------------------------------------------------------------------ # -# bridge message rules; -# - if distinct must be absolute and self contained as a later message will supercede any earlier ones in the outgoing queue. -# - use the message type and ident to identify distinct messages -# The outgoing 'port' is a multiplay/generic/string index. -# - ! is used as a seperator between the elements that are used to send the notification (typeid, sequence, notification) +# NOTES: Aircraft do not need to have both an incoming and outgoing bridge, but it is usual. +# Only the notifications specified will be routed via the bridge. +# Once routed a message will by default not be re-rerouted again by the outgoing bridge. +# Transmit frequency and message lifetime may need to be tuned. +# IsDistinct messages must be absolute and self contained as a later message will +# supercede any earlier ones in the outgoing queue (possibly prior to receipt) +# Use the message type and ident to identify distinct messages +# The outgoing 'port' is a multiplay/generic/string index. +# - ! is used as a seperator between the elements that are used to send the +# notification (typeid, sequence, notification) +# - There is an extra ! at the start of the message that is used to indicate protocol version. # - ; is used to seperate serialzied elemnts of the notification -# -# Outgoing messages are sent in a scheduled manner, usually once per second, and each message has a lifetime (to allow for propogation to -# all clients over UDP). Clients will ignore messages that they have already received (based on the sequence id). -# -# The incoming bridge will usually be created part of the aircraft model file; it is important to understand that each AI/MP model will have an incoming bridge -# as each element in /ai/models needs its own bridge to keep up with the incoming sequence id. This scheme may not work properly as it relies on the model being -# loaded which may only happen when visible so it may be necessary to track AI models in a seperate instantiatable incoming bridge manager. + +# General Notes +#---------------------------------------------------------------------- +# Outgoing messages are sent in a scheduled manner, usually once per +# second, and each message has a lifetime (to allow for propogation to +# all clients over UDP). Clients will ignore messages that they have +# already received (based on the sequence id). + +# The incoming bridge will usually be created part of the aircraft +# model file; it is important to understand that each AI/MP model will +# have an incoming bridge as each element in /ai/models needs its own +# bridge to keep up with the incoming sequence id. This scheme may not +# work properly as it relies on the model being loaded which may only +# happen when visible so it may be necessary to track AI models in a +# seperate instantiatable incoming bridge manager. # # The outgoing bridge would usually be created within the aircraft loading Nasal. + var OutgoingMPBridge = { - new: func(_ident, _notifications_to_bridge=nil, _mpidx=19, _root="", _transmitter=nil) + new: func(_ident, _notifications_to_bridge=nil, _mpidx=18, _root="", _transmitter=nil) { if (_transmitter == nil) _transmitter = emesary.GlobalTransmitter; print("OutgoingMPBridge created for "~_ident); - var new_class = emesary.Recipient.new("OutgoingMPBridge "~_ident); new_class.MessageIndex = 100; @@ -33,6 +74,9 @@ var OutgoingMPBridge = else new_class.NotificationsToBridge = _notifications_to_bridge; + foreach(var n ; new_class.NotificationsToBridge) + print(" bridge --> ",n.NotificationType); + new_class.MPout = ""; new_class.MPidx = _mpidx; new_class.MessageLifeTime = 10; # seconds @@ -42,6 +86,7 @@ var OutgoingMPBridge = new_class.Transmitter.Register(new_class); new_class.MpVariable = _root~"sim/multiplay/generic/string["~new_class.MPidx~"]"; new_class.TransmitterActive = 0; + new_class.TransmitFrequencySeconds = 1; new_class.TransmitTimer = maketimer(6, func @@ -49,9 +94,8 @@ var OutgoingMPBridge = if(new_class.TransmitterActive) new_class.Transmit(); - new_class.TransmitTimer.restart(1); + new_class.TransmitTimer.restart(new_class.TransmitFrequencySeconds); }); - new_class.TransmitTimer.restart(1); new_class.Delete = func { @@ -72,20 +116,20 @@ var OutgoingMPBridge = if (notification.FromIncomingBridge) return emesary.Transmitter.ReceiptStatus_NotProcessed; -#print("Receive ",notification.Type," (",notification.Ident); +print("Receive ",notification.NotificationType," ",notification.Ident); for (var idx = 0; idx < size(me.NotificationsToBridge); idx += 1) { - if(me.NotificationsToBridge[idx].Type == notification.Type) + if(me.NotificationsToBridge[idx].NotificationType == notification.NotificationType) { me.MessageIndex += 1; notification.MessageExpiryTime = systime()+me.MessageLifeTime; notification.BridgeMessageId = me.MessageIndex; - notification.BridgeMessageTypeId = idx; + notification.BridgeMessageNotificationTypeId = idx; # # The message key is a composite of the type and ident to allow for multiple senders # of the same message type. - notification.BridgeMessageTypeKey = notification.Type~"."~notification.Ident; -#print("Received ",notification.BridgeMessageTypeKey," expire=",notification.MessageExpiryTime); + notification.BridgeMessageNotificationTypeKey = notification.NotificationType~"."~notification.Ident; +#print("Received ",notification.BridgeMessageNotificationTypeKey," expire=",notification.MessageExpiryTime); me.AddToOutgoing(notification); return emesary.Transmitter.ReceiptStatus_Pending; } @@ -98,16 +142,16 @@ var OutgoingMPBridge = { for (var idx = 0; idx < size(me.OutgoingList); idx += 1) { - if(me.OutgoingList[idx].BridgeMessageTypeKey == notification.BridgeMessageTypeKey) + if(me.OutgoingList[idx].BridgeMessageNotificationTypeKey == notification.BridgeMessageNotificationTypeKey) { -#print("Update ",me.OutgoingList[idx].BridgeMessageTypeKey); +#print("Update ",me.OutgoingList[idx].BridgeMessageNotificationTypeKey); me.OutgoingList[idx]= notification; me.TransmitterActive = size(me.OutgoingList); return; } } } -#print("Added ",notification.BridgeMessageTypeKey); +#print("Added ",notification.BridgeMessageNotificationTypeKey); append(me.OutgoingList, notification); me.TransmitterActive = size(me.OutgoingList); }; @@ -133,7 +177,7 @@ var OutgoingMPBridge = #print("Encode ",eidx,"=",encval); eidx += 1; } - sect = sprintf("%d!%d!%s",notification.BridgeMessageId, notification.BridgeMessageTypeId, encval); + sect = sprintf("!%d!%d!%s",notification.BridgeMessageId, notification.BridgeMessageNotificationTypeId, encval); outgoing = outgoing~sect; me.OutgoingList[out_idx] = me.OutgoingList[idx]; # print("xmit ",idx," out=",out_idx); @@ -150,6 +194,7 @@ var OutgoingMPBridge = for(var del_i=0; del_i < del_count; del_i += 1) pop(me.OutgoingList); }; + new_class.TransmitTimer.restart(new_class.TransmitFrequencySeconds); return new_class; }, }; @@ -160,7 +205,7 @@ var OutgoingMPBridge = # route messages to var IncomingMPBridge = { - new: func(_ident, _notifications_to_bridge=nil, _mpidx=19, _transmitter=nil) + new: func(_ident, _notifications_to_bridge=nil, _mpidx=18, _transmitter=nil) { if (_transmitter == nil) _transmitter = emesary.GlobalTransmitter; @@ -216,12 +261,12 @@ var IncomingMPBridge = { # get the message parts var encoded_notification = split("!", encoded_val); - if (size(encoded_notification) < 3) + if (size(encoded_notification) < 4) print("Error: emesary.IncomingBridge.ProcessIncoming bad msg ",encoded_val); else { - var msg_idx = encoded_notification[0]; - var msg_type_id = encoded_notification[1]; + var msg_idx = encoded_notification[1]; + var msg_type_id = encoded_notification[2]; if (msg_type_id >= size(me.NotificationsToBridge)) { print("Error: emesary.IncomingBridge.ProcessIncoming invalid type_id ",msg_type_id); @@ -229,14 +274,14 @@ var IncomingMPBridge = else { var msg = me.NotificationsToBridge[msg_type_id]; - var msg_notify = encoded_notification[2]; - print("received idx=",msg_idx," ",msg_type_id,":",msg.Type); + var msg_notify = encoded_notification[3]; + print("received idx=",msg_idx," ",msg_type_id,":",msg.NotificationType); if(msg_idx <= me.IncomingMessageIndex) print(" **Already processed"); else { # raise notification - var bridged_notification = msg; #emesary.Notification.new(msg.Type,"BridgedMessage"); + var bridged_notification = msg; #emesary.Notification.new(msg.NotificationType,"BridgedMessage"); # populate fields var bridgedProperties = msg.bridgeProperties(); var encvals=split(";", msg_notify); @@ -267,30 +312,45 @@ var IncomingMPBridge = } foreach(var n; new_class.NotificationsToBridge) { - print("IncomingBridge: ",n.Type); + print("IncomingBridge: ",n.NotificationType); } return new_class; }, + # + # Each multiplayer object will have its own incoming bridge. This is necessary to allow message ID + # tracking (as the bridge knows which messages have been already processed) + # Whenever a client connects over MP a new bridge is instantiated startMPBridge : func(notification_list) { var incomingBridgeList = {}; + + # + # Create bridge whenever a client connects + # setlistener("/ai/models/model-added", func(v) { -#Model added /ai[0]/models[0]/multiplayer[0] + # Model added will be eg: /ai[0]/models[0]/multiplayer[0] var path = v.getValue(); + print("Model added ",path); + # Ensure we only handle multiplayer elements if (find("/multiplayer",path) > 0) { var callsign = getprop(path~"/callsign"); print("Creating Emesary MPBridge for ",callsign); if (callsign == "" or callsign == nil) callsign = path; - var incomingBridge = emesary_mp_bridge.IncomingMPBridge.new(callsign~"mp", notification_list, 19); + + var incomingBridge = emesary_mp_bridge.IncomingMPBridge.new(callsign~"mp", notification_list, 18); + incomingBridge.Connect(path~"/"); incomingBridgeList[path] = incomingBridge; } }); + # + # when a client disconnects remove the associated bridge. + # setlistener("/ai/models/model-removed", func(v){ print("Model removed ",v.getValue()); var path = v.getValue(); @@ -303,17 +363,3 @@ var IncomingMPBridge = }); }, }; -#to setup bridges; -#io.load_nasal(getprop("/sim/fg-root") ~ "/Nasal/emesary_mp_bridge.nas"); -#var outgoingBridge = emesary_mp_bridge.OutgoingMPBridge.new("F-15mp"); -#outgoingBridge.AddMessage(an_spn_46.ANSPN46ActiveNotification.new("template")); -#incomingBridge = emesary_mp_bridge.IncomingMPBridge.new("F-15mp"); -#incomingBridge.AddMessage(an_spn_46.ANSPN46ActiveNotification.new("template")); -#var outgoingBridge = emesary_mp_bridge.OutgoingMPBridge.new("F-15mp"); -#outgoingBridge.AddMessage(an_spn_46.ANSPN46ActiveNotification.new("template")); -# var m = emesary.notifications.TacticalNotification("rr",2); -# emesary.GlobalTransmitter.NotifyAll(m); -#var outgoingBridge = emesary_mp_bridge.OutgoingMPBridge.new("F-15mp"); -#outgoingBridge.AddMessage(notifications.TacticalNotification.new(nil)); -# var m = emesary.notifications.TacticalNotification("rr",2); -# emesary.GlobalTransmitter.NotifyAll(m);