1
0
Fork 0

Emesary multiplayer bridge rework.

The primary goal of this work is to provide a alternative to multiplayer property transmission, one that is more efficient and event driven. By using notifications in the model bindings these control messages can be bridged and do not require extra code to implement dual controls.

Using this alternative permits the modeller much greater control of which properties are transmitted, how these are encoded, and how often the updates are needed.

Please remember that this is a use case for Emesary, it is not all that Emesary can provide and there are still many other uses for the event driven object oriented system that Emesary provides.

Briefly the changes are:

- New PropertySyncNotificationBase to permit property transfer between ownship and MP versions using notifications instead of the pre-defined MP messages. This gives a much more flexible way to seutp multiplayer property transfer - and can probably support more properties because of the more efficient packing. There is a corresponding parameter (/sim/multiplay/transmit-only-generics) that will only transmit the bare minimum of parameters; making more space for notifications.

- new AircraftControlNotification to allow for animation bindings in the model, these notifications can be easily bridged over MP thus adding a simple method to implement dual controls.

- more efficient packing of encoded notifications over MP saving roughly 30% (by using a much larger encoding space and also changing to use fixed length encoding). This breaks compatibility with previous MP bridges, however at this time I don't think anything is using the current code.

There can be different types of PropertySyncNotification (with unique ID's) sent at a different schedule (so you could have a 10 second update of very slow moving items). However bear in mind that the messages have a lifetime of 10 seconds to ensure receipt - so to optimise space would require > 15 seconds to make much difference, and that is very slow moving.
This commit is contained in:
Richard Harrison 2017-07-23 16:04:46 +02:00
parent 19f8adfcfe
commit 405eb73bb6
3 changed files with 436 additions and 157 deletions

View file

@ -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));

View file

@ -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;
}
});
},
};

View file

@ -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},
},
];
};