diff --git a/Nasal/emesary.nas b/Nasal/emesary.nas index 54d07e0e9..7e7c3444c 100644 --- a/Nasal/emesary.nas +++ b/Nasal/emesary.nas @@ -1,4 +1,4 @@ - #--------------------------------------------------------------------------- +#--------------------------------------------------------------------------- # # Title : EMESARY inter-object communication # @@ -10,7 +10,7 @@ # : 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 -# : http://www.chateau-logic.com/content/class-based-inter-object-communication + # : http://www.chateau-logic.com/content/class-based-inter-object-communication # : http://chateau-logic.com/content/emesary-efficient-inter-object-communication-using-interfaces-and-inheritance # : http://chateau-logic.com/content/c-wpf-application-plumbing-using-emesary # @@ -162,9 +162,10 @@ var Transmitter = # or ATC acknowledgements that all need to be transmitted # The IsDistinct is important for any messages that are bridged over MP as # only the most recently sent distinct message will be transmitted over MP +var NotificationAutoTypeId = 1; var Notification = { - new: func(_type, _ident) + new: func(_type, _ident, _typeid=0) { var new_class = { parents: [Notification]}; new_class.Ident = _ident; @@ -172,6 +173,12 @@ var Notification = new_class.IsDistinct = 1; new_class.FromIncomingBridge = 0; new_class.Callsign = nil; + if (_typeid == 0) + { + _typeid = NotificationAutoTypeId; + NotificationAutoTypeId = NotificationAutoTypeId + 1; + } + new_class.TypeId = _typeid; return new_class; }, }; @@ -215,66 +222,76 @@ var GlobalTransmitter = Transmitter.new("GlobalTransmitter"); # This is basically a base64 like encode except we just use alphanumerics which gives us a base62 encode. var BinaryAsciiTransfer = { - alphabet : "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", - encodeInt : func(num) +# alphabet : "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + alphabet : chr(1)~chr(2)~chr(3)~chr(4)~chr(5)~chr(6)~chr(7)~chr(8)~chr(9)~chr(10)~chr(11)~chr(12)~chr(13) + ~chr(14)~chr(15)~chr(16)~chr(17)~chr(18)~chr(19)~chr(20)~chr(21)~chr(22)~chr(23)~chr(24)~chr(25) + ~chr(26)~chr(27)~chr(28)~chr(29)~chr(30)~chr(31)~chr(34) + ~"%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}" + ~chr(128)~chr(129)~chr(130)~chr(131)~chr(132)~chr(133)~chr(134)~chr(135)~chr(136)~chr(137)~chr(138) + ~chr(139)~chr(140)~chr(141)~chr(142)~chr(143)~chr(144)~chr(145)~chr(146)~chr(147)~chr(148)~chr(149) + ~chr(150)~chr(151)~chr(152)~chr(153)~chr(154)~chr(155)~chr(156)~chr(157)~chr(158)~chr(159)~chr(160) + ~chr(161)~chr(162)~chr(163)~chr(164)~chr(165)~chr(166)~chr(167)~chr(168)~chr(169)~chr(170)~chr(171) + ~chr(172)~chr(173)~chr(174)~chr(175)~chr(176)~chr(177)~chr(178)~chr(179)~chr(180)~chr(181)~chr(182) + ~chr(183)~chr(184)~chr(185)~chr(186)~chr(187)~chr(188)~chr(189)~chr(190)~chr(191)~chr(192)~chr(193) + ~chr(194)~chr(195)~chr(196)~chr(197)~chr(198)~chr(199)~chr(200)~chr(201)~chr(202)~chr(203)~chr(204) + ~chr(205)~chr(206)~chr(207)~chr(208)~chr(209)~chr(210)~chr(211)~chr(212)~chr(213)~chr(214)~chr(215) + ~chr(216)~chr(217)~chr(218)~chr(219)~chr(220)~chr(221)~chr(222)~chr(223)~chr(224)~chr(225)~chr(226) + ~chr(227)~chr(228)~chr(229)~chr(230)~chr(231)~chr(232)~chr(233)~chr(234)~chr(235)~chr(236)~chr(237) + ~chr(238)~chr(239)~chr(240)~chr(241)~chr(242)~chr(243)~chr(244)~chr(245)~chr(246)~chr(247)~chr(248) + ~chr(249)~chr(250)~chr(251)~chr(252)~chr(253)~chr(254)~chr(255), + _base: 248, + spaces: " ", + empty_encoding: chr(1)~chr(1)~chr(1)~chr(1)~chr(1)~chr(1)~chr(1)~chr(1)~chr(1)~chr(1)~chr(1), + encodeInt : func(num,length) { if (num == 0) - return substr(BinaryAsciiTransfer.alphabet,0,1); + return substr(BinaryAsciiTransfer.empty_encoding,0,length); var arr=""; - var _base = size(BinaryAsciiTransfer.alphabet); - while(num > 0) - { - var rem = math.mod(num,_base); - num = int(num / _base); + while (num > 0 and length > 0) { + var num0 = num; + num = (int)(num / BinaryAsciiTransfer._base); + rem = num0-(num*BinaryAsciiTransfer._base); arr =substr(BinaryAsciiTransfer.alphabet, rem,1) ~ arr; + length -= 1; } + if (length>0) + arr = substr(BinaryAsciiTransfer.spaces,0,length)~arr; return arr; }, - decodeInt : func(v) + retval : {value:0, pos:0}, + decodeInt : func(str, length, pos) { - var _base = size(BinaryAsciiTransfer.alphabet); - var power = size(v) - 1; - var num = 0; - - var idx = 0; - for(var idx=0; idx < size(v); idx += 1) - { - var c = substr(v,idx,1); - var cc = find(c,BinaryAsciiTransfer.alphabet); - if (cc < 0) - return nil; - num += int(cc * math.exp(math.ln(_base) * power)); - - power -= 1; + var power = length-1; + BinaryAsciiTransfer.retval.value = 0; + BinaryAsciiTransfer.retval.pos = pos; + while (length > 0 and power > 0) { + var c = substr(str,BinaryAsciiTransfer.retval.pos,1); + if (c != " ") break; + power = power -1; + length = length-1; + BinaryAsciiTransfer.retval.pos = BinaryAsciiTransfer.retval.pos + 1; } - return num; + while (length >= 0 and power >= 0) { + var c = substr(str,BinaryAsciiTransfer.retval.pos,1); + # spaces are used as padding so ignore them. + if (c != " ") { + var cc = find(c,BinaryAsciiTransfer.alphabet); + if (cc < 0) + { + print("Emesary: BinaryAsciiTransfer.decodeInt: Bad encoding "); + return BinaryAsciiTransfer.retval; + } + BinaryAsciiTransfer.retval.value += int(cc * math.exp(math.ln(BinaryAsciiTransfer._base) * power)); + power = power - 1; + } + length = length-1; + BinaryAsciiTransfer.retval.pos = BinaryAsciiTransfer.retval.pos + 1; + } + return BinaryAsciiTransfer.retval; } }; -var TransferCoord = -{ -# 28 bits = 268435456 (268 435 456) -# to transfer lat lon (360 degree range) 268435456/360=745654 -# we could use different factors for lat lon due to the differing range, however -# this will be fine. - encode : func(v) - { - return BinaryAsciiTransfer.encodeInt( (v.lat()+90)*745654) ~ "." ~ - BinaryAsciiTransfer.encodeInt((v.lon()+180)*745654) ~ "." ~ - BinaryAsciiTransfer.encodeInt((v.alt()+1400)*100); - } - , - decode : func(v) - { - var parts = split(".", v); - var lat = (BinaryAsciiTransfer.decodeInt(parts[0]) / 745654)-90; - var lon = (BinaryAsciiTransfer.decodeInt(parts[1]) / 745654)-180; - var alt = (BinaryAsciiTransfer.decodeInt(parts[2]) / 100)-1400; - return geo.Coord.new().set_latlon(lat, lon).set_alt(alt); - } -}; - var TransferString = { # @@ -288,10 +305,12 @@ var TransferString = }, encode : func(v) { + if (v==nil) + return "0"; var l = size(v); if (l > 16) l = 16; - var rv = BinaryAsciiTransfer.encodeInt(l); + var rv = BinaryAsciiTransfer.encodeInt(l,1); for(var ii = 0; ii < l; ii = ii + 1) { @@ -301,11 +320,71 @@ var TransferString = } return rv; }, - decode : func(v) + decode : func(v,pos) { - var l = BinaryAsciiTransfer.decodeInt(v); - var rv = substr(v,1,l-1); - return rv; + var dv = BinaryAsciiTransfer.decodeInt(v,1,pos); + var length = dv.value; + var rv = substr(v,dv.pos,length); + dv.pos = dv.pos + length; + dv.value = rv; + return dv; + } +}; + +# +# encode an int into a specified number of characters. +var TransferInt = +{ + encode : func(v, length) + { + return BinaryAsciiTransfer.encodeInt(v,length); + }, + decode : func(v, length, pos) + { + return BinaryAsciiTransfer.decodeInt(v,length,pos); + } +}; + +var TransferFixedDouble = +{ + po2: [1.0,124.0,30752.0,7626496.0,1891371008.0,469060009984.0,116326882476032.0,28849066854055936.0], # needs to match powers of BinaryAsciiTransfer._base + + encode : func(v, length, factor) + { + var scale = int(me.po2[length] / factor); + var v = int(v * factor); + if (v < -scale) v = -scale; + else if (v > scale) v = scale; + return BinaryAsciiTransfer.encodeInt(int(v), length); + }, + decode : func(v, length, factor, pos) + { + var scale = int(me.po2[length] / factor); + var dv = BinaryAsciiTransfer.decodeInt(v, length, pos); + dv.value = (int(dv.value)/factor); + return dv; + } +}; + +var TransferNorm = +{ + powers: [1,10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 10000000.0, 100000000.0, 1000000000.0, 10000000000.0, 100000000000.0], + po2: [1.0,123.0,30751.0,7626495.0,1891371007,469060009983,116326882476031,28849066854055935], # needs to match powers of BinaryAsciiTransfer._base + + encode : func(v, length) + { + v = v + 1; + if(v>2) + v=2; + else if (v < 0) + v=0; + return BinaryAsciiTransfer.encodeInt(int(v * me.po2[length]),length); + }, + decode : func(v, length, pos) + { + dv = BinaryAsciiTransfer.decodeInt(v, length,pos); + dv.value = (dv.value/me.po2[length]) - 1; + return dv; } }; @@ -313,34 +392,43 @@ var TransferByte = { encode : func(v) { - return BinaryAsciiTransfer.encodeInt(v); + return BinaryAsciiTransfer.encodeInt(v,1); }, - decode : func(v) + decode : func(v, pos) { - return BinaryAsciiTransfer.decodeInt(v); + return BinaryAsciiTransfer.decodeInt(v, 1,pos); } }; -var TransferInt = +var TransferCoord = { +# 28 bits = 268435456 (268 435 456) +# to transfer lat lon (360 degree range) 268435456/360=745654 +# we could use different factors for lat lon due to the differing range, however +# this will be fine. +# 1 degree = 110574 meters; encode : func(v) { - return BinaryAsciiTransfer.encodeInt(v); + return BinaryAsciiTransfer.encodeInt((v.lat()+90)*745654,5) + ~ BinaryAsciiTransfer.encodeInt((v.lon()+180)*745654,5) + ~ TransferInt.encode(v.alt(), 3); }, - decode : func(v) + decode : func(v,pos) { - return BinaryAsciiTransfer.decodeInt(v); - } -}; + var dv = BinaryAsciiTransfer.decodeInt(v,5,pos); + var lat = (dv.value / 745654)-90; + dv = BinaryAsciiTransfer.decodeInt(v,5,dv.pos); + var lon = (dv.value / 745654)-180; + dv = TransferInt.decode(v, 3, dv.pos); + var alt =dv.value; -var TransferFixedDouble = -{ - encode : func(v, precision=4) - { - return BinaryAsciiTransfer.encodeInt(v*math.exp(math.ln(10) * precision)); - }, - decode : func(v, precision=4) - { - return BinaryAsciiTransfer.decodeInt(v)/math.exp(math.ln(10) * precision); + dv.value = geo.Coord.new().set_latlon(lat, lon).set_alt(alt); + return dv; } }; +#setprop("/sim/startup/terminal-ansi-colors",0); +#for(i=-1;i<=1;i+=0.1) +#print ("i ",i, " --> ", (TransferNorm.decode(TransferNorm.encode(i,2), 2,0)).value); +#debug.dump(TransferNorm.decode(TransferNorm.encode(-1,2), 2,0)); +#debug.dump(TransferNorm.decode(TransferNorm.encode(0,2), 2,0)); +#debug.dump(TransferNorm.decode(TransferNorm.encode(1,2), 2,0)); diff --git a/Nasal/emesary_mp_bridge.nas b/Nasal/emesary_mp_bridge.nas index 9547116d4..3acd27bd7 100644 --- a/Nasal/emesary_mp_bridge.nas +++ b/Nasal/emesary_mp_bridge.nas @@ -59,10 +59,12 @@ var EmesaryMPBridgeDefaultPropertyIndex=19; var OutgoingMPBridge = { - SeperatorChar : "!", - StartMessageIndex : 11, - DefaultMessageLifetime : 10, - MPStringMaxLen: 50, + SeperatorChar : "!", + MessageEndChar : "~", + StartMessageIndex : 11, + DefaultMessageLifetimeSeconds : 10, + MPStringMaxLen: 128, + new: func(_ident, _notifications_to_bridge=nil, _mpidx=19, _root="", _transmitter=nil) { if (_transmitter == nil) @@ -71,17 +73,20 @@ var OutgoingMPBridge = print("OutgoingMPBridge created for "~_ident," mp=",_mpidx); var new_class = emesary.Recipient.new("OutgoingMPBridge "~_ident); - new_class.MessageIndex = OutgoingMPBridge.StartMessageIndex; -# foreach (var notification; _notifications_to_bridge) -# new_class.NotificationsToBridge = notification.new(; + if(_notifications_to_bridge == nil) new_class.NotificationsToBridge = []; else new_class.NotificationsToBridge = _notifications_to_bridge; - foreach(var n ; new_class.NotificationsToBridge) - print(" bridge --> ",n.NotificationType); + new_class.NotificationsToBridge_Lookup = {}; + foreach(var n ; new_class.NotificationsToBridge) + { + print(" Outward bridge notifications of type --> ",n.NotificationType); + n.MessageIndex = OutgoingMPBridge.StartMessageIndex; + new_class.NotificationsToBridge_Lookup[n.TypeId] = n; + } new_class.MPout = ""; new_class.MPidx = _mpidx; new_class.MessageLifeTime = 10; # seconds @@ -126,10 +131,11 @@ var OutgoingMPBridge = { if(me.NotificationsToBridge[idx].NotificationType == notification.NotificationType) { - me.MessageIndex += 1; + me.NotificationsToBridge[idx].MessageIndex += 1; notification.MessageExpiryTime = systime()+me.MessageLifeTime; - notification.BridgeMessageId = me.MessageIndex; - notification.BridgeMessageNotificationTypeId = idx; + notification.Expired = 0; + notification.BridgeMessageId = me.NotificationsToBridge[idx].MessageIndex; + notification.BridgeMessageNotificationTypeId = me.NotificationsToBridge[idx].TypeId; # # The message key is a composite of the type and ident to allow for multiple senders # of the same message type. @@ -164,50 +170,86 @@ var OutgoingMPBridge = { var outgoing = ""; var cur_time=systime(); - var out_idx = 0; for (var idx = 0; idx < size(me.OutgoingList); idx += 1) { var sect = ""; var notification = me.OutgoingList[idx]; - if (notification.MessageExpiryTime > cur_time) + if (!notification.Expired and notification.MessageExpiryTime > cur_time) { var encval=""; var first_time = 1; var eidx = 0; + notification.Expired = 0; foreach(var p ; notification.bridgeProperties()) { - if (encval != "") - encval = encval ~ ";"; - encval = encval ~ p.getValue(); -#print("Encode ",eidx,"=",encval); + var nv = p.getValue(); + encval = encval ~ nv; eidx += 1; } - # !idx!typ!encv - sect = sprintf("%s%s%s%s%s%s", - OutgoingMPBridge.SeperatorChar, emesary.BinaryAsciiTransfer.encodeInt(notification.BridgeMessageId), - OutgoingMPBridge.SeperatorChar, emesary.BinaryAsciiTransfer.encodeInt(notification.BridgeMessageNotificationTypeId), - OutgoingMPBridge.SeperatorChar, encval); - outgoing = outgoing~sect; - me.OutgoingList[out_idx] = me.OutgoingList[idx]; -# print("xmit ",idx," out=",out_idx); - out_idx += 1; + # !idx!typ!encv~ + sect = sprintf("%s%s%s%s%s%s%s", + OutgoingMPBridge.SeperatorChar, emesary.BinaryAsciiTransfer.encodeInt(notification.BridgeMessageId,4), + OutgoingMPBridge.SeperatorChar, emesary.BinaryAsciiTransfer.encodeInt(notification.BridgeMessageNotificationTypeId,1), + OutgoingMPBridge.SeperatorChar, encval, OutgoingMPBridge.MessageEndChar); + + if (size(outgoing) + size(sect) < OutgoingMPBridge.MPStringMaxLen) + { + outgoing = outgoing~sect; + } + else + { + if(idx == 0) + print("Emesary: ERROR Notification is too large for outgoing queue: ",notification.NotificationType); +# print("outgoing buffer full: Qsize ",size(me.OutgoingList),": message lifetime extended ",notification.Ident, " xmit ",idx, " outtext=",size(outgoing), " sect=",size(sect)); + notification.MessageExpiryTime = systime()+me.MessageLifeTime; + } + } + else + { + notification.Expired = 1; } -# else -# printf("expired ",idx,out_idx); } me.TransmitterActive = size(me.OutgoingList); - var del_count = size(me.OutgoingList)-out_idx; setprop(me.MpVariable,outgoing); # print("Set ",me.MpVariable," to ",outgoing); -# print("outgoingList : ",out_idx, " ", size(me.OutgoingList), " del count=",del_count); - for(var del_i=0; del_i < del_count; del_i += 1) +#loopback test: +# incomingBridge.ProcessIncoming(outgoing); + +# +# Now remove any expired messages from the outgoing queue. +# The only real way of doing this is via the pop() function +# so we have to delete the expired items from the list by rebuilding +# the list from the start with non-expired items, and then +# all of the items past the end (of the rebuilt list) can be popped +# (pop removes the last element from a vector) + var outSize = size(me.OutgoingList)-1; + var out_idx = 0; + for (var idx = 0; idx <= outSize; idx += 1) + { +#print("Q1 [",idx,"] ",me.OutgoingList[idx].MessageExpiryTime-cur_time," Expired=",me.OutgoingList[idx].Expired); + if(!me.OutgoingList[idx].Expired) + { +#print("move ",idx, " => ",out_idx); + var mmove = me.OutgoingList[idx]; + me.OutgoingList[out_idx] = me.OutgoingList[idx]; + out_idx += 1; + } + } + var to_del = (outSize+1) - out_idx; +#print("--> out idx",out_idx, " to delete ",to_del); + while(to_del > 0) + { +#print("--> pop "); pop(me.OutgoingList); + to_del = to_del - 1; + } }; new_class.TransmitTimer.restart(new_class.TransmitFrequencySeconds); return new_class; }, }; + # # # one of these for each model instantiated in the model XML - it will @@ -219,19 +261,26 @@ var IncomingMPBridge = if (_transmitter == nil) _transmitter = emesary.GlobalTransmitter; - print("IncominggMPBridge created for "~_ident," mp=",_mpidx); + print("IncominggMPBridge created for "~_ident," mp=",_mpidx, " using Transmitter ",_transmitter.Ident); var new_class = emesary.Transmitter.new("IncominggMPBridge "~_ident); - new_class.IncomingMessageIndex = OutgoingMPBridge.StartMessageIndex; if(_notifications_to_bridge == nil) new_class.NotificationsToBridge = []; else new_class.NotificationsToBridge = _notifications_to_bridge; + new_class.NotificationsToBridge_Lookup = {}; + + foreach(var n ; new_class.NotificationsToBridge) + { + print(" Incoming bridge notification type --> ",n.NotificationType); + n.IncomingMessageIndex = OutgoingMPBridge.StartMessageIndex; + new_class.NotificationsToBridge_Lookup[n.TypeId] = n; + } new_class.MPout = ""; new_class.MPidx = _mpidx; - new_class.MessageLifeTime = OutgoingMPBridge.DefaultMessageLifetime; # seconds + new_class.MessageLifeTime = OutgoingMPBridge.DefaultMessageLifetimeSeconds; # seconds new_class.OutgoingList = []; new_class.Transmitter = _transmitter; new_class.MpVariable = ""; @@ -240,8 +289,10 @@ var IncomingMPBridge = { me.MpVariable = _root~"sim/multiplay/generic/string["~new_class.MPidx~"]"; me.CallsignPath = _root~"callsign"; + me.PropertyRoot = _root; setlistener(me.MpVariable, func(v) { +#print("incoming -->",v.getValue()); me.ProcessIncoming(v.getValue()); }); }; @@ -265,54 +316,61 @@ var IncomingMPBridge = new_class.ProcessIncoming = func(encoded_val) { if (encoded_val == "") - return; + return; # print("ProcessIncoming ", encoded_val); - var encoded_notifications = split(";", encoded_val); + var encoded_notifications = split(OutgoingMPBridge.MessageEndChar, encoded_val); for (var idx = 0; idx < size(encoded_notifications); idx += 1) { + if (encoded_notifications[idx] == "") + continue; # get the message parts - var encoded_notification = split(OutgoingMPBridge.SeperatorChar, encoded_val); + var encoded_notification = split(OutgoingMPBridge.SeperatorChar, encoded_notifications[idx]); if (size(encoded_notification) < 4) - print("Error: emesary.IncomingBridge.ProcessIncoming bad msg ",encoded_val); + print("Error: emesary.IncomingBridge.ProcessIncoming bad msg ",encoded_notifications[idx]); else { - var msg_idx = emesary.BinaryAsciiTransfer.decodeInt(encoded_notification[1]); - var msg_type_id = emesary.BinaryAsciiTransfer.decodeInt(encoded_notification[2]); - if (msg_type_id >= size(me.NotificationsToBridge)) + var msg_idx = emesary.BinaryAsciiTransfer.decodeInt(encoded_notification[1],4,0).value; + var msg_type_id = emesary.BinaryAsciiTransfer.decodeInt(encoded_notification[2],1,0).value; + var bridged_notification = new_class.NotificationsToBridge_Lookup[msg_type_id]; + if (bridged_notification == nil) { print("Error: emesary.IncomingBridge.ProcessIncoming invalid type_id ",msg_type_id); } else { - var msg = me.NotificationsToBridge[msg_type_id]; - var msg_notify = encoded_notification[3]; -# print("received idx=",msg_idx," ",msg_type_id,":",msg.NotificationType); - if (msg_idx > me.IncomingMessageIndex) + if (msg_idx > bridged_notification.IncomingMessageIndex) { - # raise notification - var bridged_notification = msg; #emesary.Notification.new(msg.NotificationType,"BridgedMessage"); + var msg_body = encoded_notification[3]; +#print("received idx=",msg_idx," ",msg_type_id,":",bridged_notification.NotificationType); # populate fields - var bridgedProperties = msg.bridgeProperties(); - var encvals=split(";", msg_notify); - - for (var bpi = 0; bpi < size(encvals); bpi += 1) { - if (bpi < size(bridgedProperties)) { + var bridgedProperties = bridged_notification.bridgeProperties(); + var msglen = size(msg_body); +#print("Process ",msg_body," len=",msglen, " BPsize = ",size(bridgedProperties)); + var bridgePropertyIndex = 0; + var pos = 0; + for (var bpi = 0; bpi < size(bridgedProperties); bpi += 1) + { + if (pos < msglen) + { var bp = bridgedProperties[bpi]; - if (encvals[bpi] != ";" and encvals[bpi] != "") { - var bp = bridgedProperties[bpi]; - bp.setValue(encvals[bpi]); - } - #else - #print("EMPTY encoded ",bpi," empty"); - } else - print("Error: emesary.IncomingBridge.ProcessIncoming: supplementary encoded value at",bpi); + dv = bp.setValue(msg_body, me, pos); + if (dv.pos == pos or dv.pos > msglen) + break; +#debug.dump(dv); + pos = dv.pos; + } + else + { + print("Error: emesary.IncomingBridge.ProcessIncoming: supplementary encoded value at",bridgePropertyIndex); + break; + } } if (bridged_notification.Ident == "none") - bridged_notification.Ident = "mp-bridge"; + bridged_notification.Ident = "mp-bridge"; bridged_notification.FromIncomingBridge = 1; bridged_notification.Callsign = me.GetCallsign(); me.Transmitter.NotifyAll(bridged_notification); - me.IncomingMessageIndex = msg_idx; + bridged_notification.IncomingMessageIndex = msg_idx; } } } @@ -328,7 +386,7 @@ var IncomingMPBridge = # 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) + startMPBridge : func(notification_list, mpidx=19, transmitter=nil) { var incomingBridgeList = {}; @@ -348,7 +406,7 @@ var IncomingMPBridge = if (callsign == "" or callsign == nil) callsign = path; - var incomingBridge = emesary_mp_bridge.IncomingMPBridge.new(path, notification_list); + var incomingBridge = emesary_mp_bridge.IncomingMPBridge.new(path, notification_list, mpidx, transmitter); incomingBridge.Connect(path~"/"); incomingBridgeList[path] = incomingBridge; @@ -360,13 +418,13 @@ var IncomingMPBridge = # setlistener("/ai/models/model-removed", func(v){ var path = v.getValue(); - var bridge = incomingBridgeList[path]; - if (bridge != nil) - { + var bridge = incomingBridgeList[path]; + if (bridge != nil) + { # print("Bridge removed for ",v.getValue()); - bridge.Remove(); - incomingBridgeList[path]=nil; - } - }); + bridge.Remove(); + incomingBridgeList[path]=nil; + } + }); }, }; diff --git a/Nasal/notifications.nas b/Nasal/notifications.nas index c680fb8df..79988265e 100644 --- a/Nasal/notifications.nas +++ b/Nasal/notifications.nas @@ -17,11 +17,144 @@ # #---------------------------------------------------------------------------*/ -var GeoEventNotification = +# +# Ideally for notifications bridged over MP the message ID should be system unique. +# with that in mind 0-16 are reserved for model use; and may well be marked as not bridgeable +# however the most important thing is that as these notifications ID's are used the +# wiki page http://wiki.flightgear.org/Emesary_Notifications +var PropertySyncNotificationBase_Id = 16; +var AircraftControlNotification_Id = 17; +var GeoEventNotification_Id = 18; + + +# +# PropertySyncNotificationBase is a wrapper class for allow properties to be synchronized between +# modules. This can replace (or augment) the properties that are normally transmitted by multiplayer +# It is reasonably efficient with the MP2017.2 +# +# Usage example - this can all go into one Nasal module somewhere. +#----------- +# var PropertySyncNotification = +# { +# new: func(_ident="none", _name="", _kind=0, _secondary_kind=0) +# { +# var new_class = PropertySyncNotificationBase.new(_ident, _name, _kind, _secondary_kind); +# +# new_class.addIntProperty("consumables/fuel/total-fuel-lbs", 1); +# new_class.addIntProperty("controls/fuel/dump-valve", 1); +# new_class.addIntProperty("engines/engine[0]/augmentation-burner", 1); +# new_class.addIntProperty("engines/engine[0]/n1", 1); +# new_class.addIntProperty("engines/engine[0]/n2", 1); +# new_class.addNormProperty("surface-positions/wing-pos-norm", 2); +# return new_class; +# } +#}; +# +#var routedNotifications = [notifications.PropertySyncNotification.new(nil), notifications.GeoEventNotification.new(nil)]; +# +#var bridgedTransmitter = emesary.Transmitter.new("outgoingBridge"); +#var outgoingBridge = emesary_mp_bridge.OutgoingMPBridge.new("F-14mp",routedNotifications, 19, "", bridgedTransmitter); +#var incomingBridge = emesary_mp_bridge.IncomingMPBridge.startMPBridge(routedNotifications); +#var f14_aircraft_notification = notifications.PropertySyncNotification.new("F-14"~getprop("/sim/multiplay/callsign")); +#----------- +# +# That's all that is required to ship properties between multiplayer modules via emesary. +# set /sim/multiplay/transmit-only-generics to turn off all of the standard properties. this will give a packet +# size of 280 bytes; leaving lots of space for notifications. The F-14 packet size is around 53 bytes on 2017.2 +# compared to over 1100 bytes with the traditional method. +# +# The other advantage with this method of transferring data is that the model is in full control of what is +# sent, and also when it is sent. This works on a per notification basis so less important properties could be +# transmitted on a less frequent schedule; however this will require an instance of the notification for each one. +# +# PropertySyncNotificationBase is a shortcut notification; as it doesn't need to received and all +# of the properties are simply set when the notification is unpacked over MP. +# So although the notification will be transmitted +var PropertySyncNotificationBase = { new: func(_ident="none", _name="", _kind=0, _secondary_kind=0) { - var new_class = emesary.Notification.new("GeoEventNotification", _ident); + var new_class = emesary.Notification.new("PropertySyncNotification", _ident, PropertySyncNotificationBase_Id); + + new_class.IsDistinct = 1; + new_class.Kind = _kind; + new_class.Name = _name; + new_class.SecondaryKind = _secondary_kind; + new_class.Callsign = nil; # populated automatically by the incoming bridge when routed + new_class._bridgeProperties = []; + + new_class.addIntProperty = func(variable, property, length) + { + me[variable] = nil; + append(me._bridgeProperties, + { + getValue:func{return emesary.TransferInt.encode(getprop(property) or 0,length);}, + setValue:func(v,bridge,pos){var dv=emesary.TransferInt.decode(v,length,pos);me[variable]=dv.value;setprop(bridge.PropertyRoot~property, me[variable]);return dv;}, + }); + } + new_class.addNormProperty = func(variable, property, length) + { + me[variable] = nil; + append(me._bridgeProperties, + { + getValue:func{return emesary.TransferNorm.encode(getprop(property) or 0,length);}, + setValue:func(v,bridge,pos){var dv=emesary.TransferNorm.decode(v,length,pos);me[variable] = dv.value;setprop(bridge.PropertyRoot~property, me[variable]);return dv;}, + }); + } + new_class.bridgeProperties = func() + { + return me._bridgeProperties; + } + return new_class; + } +}; +# +# Transmit a generic control event. +# two parameters - the event Id and the event value which is a 4 byte length (+/- 1,891371.000) +var AircraftControlNotification = +{ + new: func(_ident="none") + { + var new_class = emesary.Notification.new("AircraftControlNotification", _ident, AircraftControlNotification_Id); + + new_class.IsDistinct = 0; + new_class.EventType = 0; + new_class.EventValue = 0; + new_class.Callsign = nil; # populated automatically by the incoming bridge when routed + + new_class.bridgeProperties = func + { + return + [ + { + getValue:func{return emesary.TransferInt.encode(new_class.EventType,2);}, + setValue:func(v,bridge,pos){var dv=emesary.TransferInt.decode(v,2,pos);new_class.EventType=dv.value;return dv;}, + }, + { + getValue:func{return emesary.TransferFixedDouble.encode(new_class.EventValue,4,1000);}, + setValue:func(v,bridge,pos){var dv=emesary.TransferFixedDouble.decode(v,4,1000,pos);new_class.EventValue=dv.value;print("dec ",dv.value);return dv;}, + }, + ]; + }; + return new_class; + } +}; + +# +# +# Use to transmit events that happen at a specific place; can be used to make +# models that are simulated locally (e.g. tankers) appear on other player's MP sessions. +var GeoEventNotification = +{ +# new: +# _ident - the identifier for the notification. not bridged. +# _name - name of the notification, bridged. +# _kind - created, moved, deleted (see below). This is the activity that the notification represents, called kind to avoid confusion with notification type. +# _secondary_kind - This is the entity on which the activity is being performed. See below for predefined types. +## + new: func(_ident="none", _name="", _kind=0, _secondary_kind=0) + { + var new_class = emesary.Notification.new("GeoEventNotification", _ident, GeoEventNotification_Id); new_class.Kind = _kind; new_class.Name = _name; @@ -41,31 +174,31 @@ var GeoEventNotification = [ { getValue:func{return emesary.TransferCoord.encode(new_class.Position);}, - setValue:func(v){new_class.Position=emesary.TransferCoord.decode(v);}, + setValue:func(v,root,pos){var dv=emesary.TransferCoord.decode(v, pos);new_class.Position=dv.value;return dv}, }, { getValue:func{return emesary.TransferString.encode(new_class.Name);}, - setValue:func(v){new_class.Name=emesary.TransferString.decode(v);}, + setValue:func(v,root,pos){var dv=emesary.TransferString.decode(v,pos);new_class.Name=dv.value;return dv}, }, { getValue:func{return emesary.TransferByte.encode(new_class.Kind);}, - setValue:func(v){new_class.Kind=emesary.TransferByte.decode(v);}, + setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.Kind=dv.value;return dv}, }, { getValue:func{return emesary.TransferByte.encode(new_class.SecondaryKind);}, - setValue:func(v){new_class.SecondaryKind=emesary.TransferByte.decode(v);}, + setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.SecondaryKind=dv.value;return dv}, }, { - getValue:func{return emesary.TransferFixedDouble.encode(new_class.u_fps);}, - setValue:func(v){new_class.u_fps=emesary.TransferFixedDouble.decode(v);}, + getValue:func{return emesary.TransferFixedDouble.encode(new_class.u_fps,2,10);}, + setValue:func(v,root,pos){var dv=emesary.TransferFixedDouble.decode(v,2,10,pos);new_class.u_fps=dv.value;return dv}, }, { - getValue:func{return emesary.TransferFixedDouble.encode(new_class.v_fps);}, - setValue:func(v){new_class.v_fps=emesary.TransferFixedDouble.decode(v);}, + getValue:func{return emesary.TransferFixedDouble.encode(new_class.v_fps,2,10);}, + setValue:func(v,root,pos){var dv=emesary.TransferFixedDouble.decode(v,2,10,pos);new_class.v_fps=dv.value;return dv}, }, { - getValue:func{return emesary.TransferFixedDouble.encode(new_class.w_fps);}, - setValue:func(v){new_class.w_fps=emesary.TransferFixedDouble.decode(v);}, + getValue:func{return emesary.TransferFixedDouble.encode(new_class.w_fps,2,10);}, + setValue:func(v,root,pos){var dv=emesary.TransferFixedDouble.decode(v,2,10,pos);new_class.w_fps=dv.value;return dv}, }, ]; };