2016-04-12 21:25:11 +00:00
#---------------------------------------------------------------------------
#
2020-07-25 12:11:07 +00:00
# Title : EMESARY multiplayer bridge
2016-04-12 21:25:11 +00:00
#
2020-07-25 12:11:07 +00:00
# File Type : Implementation File
2016-04-12 21:25:11 +00:00
#
2020-07-25 12:11:07 +00:00
# Description : Bridges selected emesary notifications over MP
# : To send a message use a Transmitter with an object. That's all there is to it.
2016-04-12 21:25:11 +00:00
#
# References : http://chateau-logic.com/content/emesary-nasal-implementation-flightgear
#
2020-07-25 12:11:07 +00:00
# Author : Richard Harrison (richard@zaretto.com)
2016-04-12 21:25:11 +00:00
#
2020-07-25 12:11:07 +00:00
# Creation Date : 04 April 2016
2016-04-12 21:25:11 +00:00
#
2020-07-25 12:11:07 +00:00
# Version : 4.8
2016-04-12 21:25:11 +00:00
#
# Copyright <20> 2016 Richard Harrison Released under GPL V2
#
#---------------------------------------------------------------------------*/
2020-07-25 12:11:07 +00:00
# Example of connecting an incoming and outgoing bridge (should reside inside an aircraft nasal file)
#
2020-10-18 22:40:05 +00:00
# var routedNotifications = [notifications.TacticalNotification.new()];
2020-07-25 12:11:07 +00:00
# var incomingBridge = emesary_mp_bridge.IncomingMPBridge.startMPBridge(routedNotifications);
# var outgoingBridge = emesary_mp_bridge.OutgoingMPBridge.new("F-15mp",routedNotifications);
#------------------------------------------------------------------
#
2018-10-31 09:05:34 +00:00
# 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
2020-07-25 12:11:07 +00:00
# supercede any earlier ones in the outgoing queue (possibly prior to receipt)
2018-10-31 09:05:34 +00:00
#
# * Use the message type and ident to identify distinct messages
#
# * The outgoing 'port' is a multiplay/emesary/bridge index, however any available string property
2020-07-25 12:11:07 +00:00
# can be used by specifying it in the construction of the incoming or outgoing bridge.
# NOTE: This should not often be changed as it different versions of FG or model will
# have to use the same properties to be able to communicate
2018-10-31 09:05:34 +00:00
#
# * multiplay/emesary/bridge-type is used to identify the bridge that is in use. This is to
2020-07-25 12:11:07 +00:00
# protect against bridges being used for different purposes by different models.
2018-10-31 09:05:34 +00:00
#
# * The bridge-type property should contain an ID that identifies the purpose
2020-07-25 12:11:07 +00:00
# and thereore the message set that the bridge will be using.
2018-10-31 09:05:34 +00:00
#
2020-07-25 12:11:07 +00:00
# - ! 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
# 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 EmesaryMPBridgeDefaultPropertyIndex=19;
2016-04-12 21:25:11 +00:00
2020-07-25 12:11:07 +00:00
var OutgoingMPBridge =
{
2017-07-23 14:04:46 +00:00
SeperatorChar : "!",
MessageEndChar : "~",
StartMessageIndex : 11,
DefaultMessageLifetimeSeconds : 10,
2020-07-25 12:11:07 +00:00
MPStringMaxLen: 128,
2017-07-23 14:04:46 +00:00
2020-07-25 12:11:07 +00:00
new: func(_ident, _notifications_to_bridge=nil, _mpidx=19, _root="", _transmitter=nil, _propertybase="emesary/bridge")
2016-04-09 19:39:57 +00:00
{
if (_transmitter == nil)
2020-07-25 12:11:07 +00:00
_transmitter = emesary.GlobalTransmitter;
2016-04-09 19:39:57 +00:00
2016-04-25 18:40:57 +00:00
print("OutgoingMPBridge created for "~_ident," mp=",_mpidx);
2016-04-09 19:39:57 +00:00
var new_class = emesary.Recipient.new("OutgoingMPBridge "~_ident);
2017-07-23 14:04:46 +00:00
2020-07-25 12:11:07 +00:00
if (_notifications_to_bridge == nil)
new_class.NotificationsToBridge = [];
2016-04-09 19:39:57 +00:00
else
2020-07-25 12:11:07 +00:00
new_class.NotificationsToBridge = _notifications_to_bridge;
2016-04-09 19:39:57 +00:00
2017-07-23 14:04:46 +00:00
new_class.NotificationsToBridge_Lookup = {};
2016-04-12 21:25:11 +00:00
2020-07-25 12:11:07 +00:00
foreach (var n ; new_class.NotificationsToBridge) {
print(" ",_ident," outwards bridge[",n,"] notifications of type --> ",n.NotificationType, " Id ",n.TypeId);
n.MessageIndex = OutgoingMPBridge.StartMessageIndex;
new_class.NotificationsToBridge_Lookup[n.TypeId] = n;
}
2016-04-09 19:39:57 +00:00
new_class.MPout = "";
new_class.MPidx = _mpidx;
new_class.MessageLifeTime = 10; # seconds
new_class.OutgoingList = [];
new_class.Transmitter = _transmitter;
new_class.TransmitRequired=0;
2017-12-28 00:25:01 +00:00
new_class.MpVariable = _root~"sim/multiplay/"~_propertybase~"["~new_class.MPidx~"]";
2016-04-09 19:39:57 +00:00
new_class.TransmitterActive = 0;
2016-04-12 21:25:11 +00:00
new_class.TransmitFrequencySeconds = 1;
2017-11-18 00:51:08 +00:00
new_class.trace = 0;
2017-11-19 12:49:15 +00:00
new_class.MPStringMaxLen = OutgoingMPBridge.MPStringMaxLen;
2016-04-09 19:39:57 +00:00
new_class.TransmitTimer =
maketimer(6, func
{
2020-07-25 12:11:07 +00:00
if (new_class.TransmitterActive)
new_class.Transmit();
2020-10-18 22:40:05 +00:00
else
new_class.TransmitEnd();
2016-04-09 19:39:57 +00:00
2016-04-12 21:25:11 +00:00
new_class.TransmitTimer.restart(new_class.TransmitFrequencySeconds);
2016-04-09 19:39:57 +00:00
});
new_class.Delete = func
{
if (me.Transmitter != nil) {
me.Transmitter.DeRegister(me);
me.Transmitter = nil;
}
};
new_class.AddMessage = func(m)
{
append(me.NotificationsToBridge, m);
2020-07-25 12:11:07 +00:00
};
2016-04-09 19:39:57 +00:00
#-------------------------------------------
# Receive override:
new_class.Receive = func(notification)
2020-07-25 12:11:07 +00:00
{
if (notification.FromIncomingBridge)
2016-04-09 19:39:57 +00:00
return emesary.Transmitter.ReceiptStatus_NotProcessed;
2020-07-25 12:11:07 +00:00
#print("Receive ",notification.NotificationType," ",notification.Ident);
for (var idx = 0; idx < size(me.NotificationsToBridge); idx += 1) {
if (me.NotificationsToBridge[idx].NotificationType == notification.NotificationType) {
me.NotificationsToBridge[idx].MessageIndex += 1;
notification.MessageExpiryTime = systime()+me.MessageLifeTime;
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.
#print("Received ",notification.BridgeMessageNotificationTypeKey," expire=",notification.MessageExpiryTime);
me.AddToOutgoing(notification);
return emesary.Transmitter.ReceiptStatus_Pending;
}
}
return emesary.Transmitter.ReceiptStatus_NotProcessed;
};
2016-04-09 19:39:57 +00:00
new_class.AddToOutgoing = func(notification)
2020-07-25 12:11:07 +00:00
{
if (notification.IsDistinct) {
for (var idx = 0; idx < size(me.OutgoingList); idx += 1) {
if (me.trace)
print("Compare [",idx,"] qId=",me.OutgoingList[idx].GetBridgeMessageNotificationTypeKey() ," noti --> ",notification.GetBridgeMessageNotificationTypeKey());
if (me.OutgoingList[idx].GetBridgeMessageNotificationTypeKey() == notification.GetBridgeMessageNotificationTypeKey()) {
if (me.trace)
print(" --> Update ",me.OutgoingList[idx].GetBridgeMessageNotificationTypeKey() ," noti --> ",notification.GetBridgeMessageNotificationTypeKey());
me.OutgoingList[idx]= notification;
me.TransmitterActive = size(me.OutgoingList);
return;
}
}
} else
if (me.trace)
print("Not distinct, adding always");
2017-11-18 00:51:08 +00:00
if (me.trace)
print(" --> Added ",notification.GetBridgeMessageNotificationTypeKey());
2016-04-09 19:39:57 +00:00
append(me.OutgoingList, notification);
me.TransmitterActive = size(me.OutgoingList);
};
new_class.Transmit = func
{
var outgoing = "";
var cur_time=systime();
2020-07-25 12:11:07 +00:00
var first_time = 1;
me.OutgoingListNew = [];
2020-05-31 03:18:04 +00:00
for (var idx = 0; idx < size(me.OutgoingList); idx += 1) {
2016-04-09 19:39:57 +00:00
var sect = "";
var notification = me.OutgoingList[idx];
2020-05-31 03:18:04 +00:00
if (!notification.Expired and notification.MessageExpiryTime > cur_time) {
2020-07-25 12:11:07 +00:00
if (notification["sect"] == nil) {
# This is first time attempting to transmit this notification
var encval="";
var eidx = 0;
notification.Expired = 0;
foreach(var p ; notification.bridgeProperties()) {
var nv = p.getValue();
encval = encval ~ nv;
eidx += 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);
} else {
# This notification has already been coded, but was previously not sent due to too little space.
sect = notification.sect;
2016-04-09 19:39:57 +00:00
}
2020-05-31 03:18:04 +00:00
if (size(outgoing) + size(sect) < me.MPStringMaxLen) {
2017-07-23 14:04:46 +00:00
outgoing = outgoing~sect;
2020-05-31 03:18:04 +00:00
} else {
2020-07-25 12:11:07 +00:00
if (first_time) {
print("Emesary: ERROR [",me.Ident,"] out of space for ",notification.NotificationType, " transmitted count=",idx, " queue size ",size(me.OutgoingList));
first_time = 0;
}
#notification.MessageExpiryTime = systime()+me.MessageLifeTime;
#break;
notification.sect = sect;
append(me.OutgoingListNew, notification);
2017-07-23 14:04:46 +00:00
}
2020-05-31 03:18:04 +00:00
} else {
2017-07-23 14:04:46 +00:00
notification.Expired = 1;
2016-04-09 19:39:57 +00:00
}
}
2020-07-25 12:11:07 +00:00
me.OutgoingList = me.OutgoingListNew;
2016-04-09 19:39:57 +00:00
me.TransmitterActive = size(me.OutgoingList);
setprop(me.MpVariable,outgoing);
};
2020-10-18 22:40:05 +00:00
new_class.TransmitEnd = func
{
if (getprop(me.MpVariable) != "") {
setprop(me.MpVariable,"");
}
};
2020-07-25 12:11:07 +00:00
new_class.Transmitter.Register(new_class);
2016-04-12 21:25:11 +00:00
new_class.TransmitTimer.restart(new_class.TransmitFrequencySeconds);
2016-04-09 19:39:57 +00:00
return new_class;
},
2020-07-25 12:11:07 +00:00
};
2017-07-23 14:04:46 +00:00
2020-07-25 12:11:07 +00:00
#
#
# one of these for each model instantiated in the model XML - it will
# route messages to
var IncomingMPBridge =
{
trace : 0,
new: func(_ident, _notifications_to_bridge=nil, _mpidx=19, _transmitter=nil, _propertybase="emesary/bridge")
2016-04-09 19:39:57 +00:00
{
if (_transmitter == nil)
2020-07-25 12:11:07 +00:00
_transmitter = emesary.GlobalTransmitter;
2016-04-09 19:39:57 +00:00
2017-12-28 00:25:01 +00:00
print("IncomingMPBridge created for "~_ident," mp=",_mpidx, " using Transmitter ",_transmitter.Ident, " with property base sim/multiplayer/"~_propertybase);
2016-04-09 19:39:57 +00:00
var new_class = emesary.Transmitter.new("IncominggMPBridge "~_ident);
2020-07-25 12:11:07 +00:00
if (_notifications_to_bridge == nil)
new_class.NotificationsToBridge = [];
2016-04-09 19:39:57 +00:00
else
2020-07-25 12:11:07 +00:00
new_class.NotificationsToBridge = _notifications_to_bridge;
2016-04-09 19:39:57 +00:00
2017-07-23 14:04:46 +00:00
new_class.NotificationsToBridge_Lookup = {};
2020-07-25 12:11:07 +00:00
foreach (var n ; new_class.NotificationsToBridge) {
2020-05-31 03:18:04 +00:00
print(" Incoming bridge notification type --> ",n.NotificationType);
var new_n = {parents: [n]};
new_n.IncomingMessageIndex = OutgoingMPBridge.StartMessageIndex;
new_class.NotificationsToBridge_Lookup[n.TypeId] = new_n;
}
2016-04-09 19:39:57 +00:00
new_class.MPout = "";
new_class.MPidx = _mpidx;
2017-12-28 00:25:01 +00:00
new_class.MPpropertyBase = _propertybase;
2017-07-23 14:04:46 +00:00
new_class.MessageLifeTime = OutgoingMPBridge.DefaultMessageLifetimeSeconds; # seconds
2016-04-09 19:39:57 +00:00
new_class.OutgoingList = [];
new_class.Transmitter = _transmitter;
new_class.MpVariable = "";
new_class.Connect = func(_root)
{
2017-12-28 00:25:01 +00:00
me.MpVariable = _root~"sim/multiplay/"~new_class.MPpropertyBase~"["~new_class.MPidx~"]";
2016-04-16 13:14:16 +00:00
me.CallsignPath = _root~"callsign";
2017-07-23 14:04:46 +00:00
me.PropertyRoot = _root;
2020-07-25 12:11:07 +00:00
me.mp_listener = setlistener(me.MpVariable, func(v)
2016-04-09 19:39:57 +00:00
{
2017-12-28 00:25:01 +00:00
#print("incoming ",getprop(me.CallsignPath)," -->",me.PropertyRoot," v=",v.getValue());
2016-04-09 19:39:57 +00:00
me.ProcessIncoming(v.getValue());
2020-05-31 03:18:04 +00:00
},0,0);
2020-07-25 12:11:07 +00:00
};
2017-12-28 00:25:01 +00:00
new_class.setprop = func(property, value){
if (IncomingMPBridge.trace == 2)
print("setprop ",new_class.PropertyRoot~property," = ",value);
setprop(new_class.PropertyRoot~property,value);
};
2016-04-16 13:14:16 +00:00
new_class.GetCallsign = func
2020-07-25 12:11:07 +00:00
{
return getprop(me.CallsignPath);
};
2016-04-09 19:39:57 +00:00
new_class.AddMessage = func(m)
{
append(me.NotificationsToBridge, m);
2020-07-25 12:11:07 +00:00
};
2016-04-09 19:39:57 +00:00
new_class.Remove = func
{
2020-07-25 12:11:07 +00:00
print("Emesary IncomingMPBridge Remove() ",me.Ident," Property: ",me.MpVariable);
2016-04-09 19:39:57 +00:00
me.Transmitter.DeRegister(me);
2020-07-25 12:11:07 +00:00
if (me["mp_listener"] != nil)
removelistener(me.mp_listener);
me.mp_listener = nil;
2016-04-09 19:39:57 +00:00
};
#-------------------------------------------
2020-07-25 12:11:07 +00:00
# Receive override:
2016-04-09 19:39:57 +00:00
new_class.ProcessIncoming = func(encoded_val)
2020-07-25 12:11:07 +00:00
{
if (encoded_val == "")
return;
2020-05-31 03:18:04 +00:00
if(right(encoded_val,1) != OutgoingMPBridge.MessageEndChar)
printf("Error: emesary.IncomingBridge.ProcessIncoming Missing endChar. From %s. Message=%s",me.GetCallsign(),encoded_val);
2020-07-25 12:11:07 +00:00
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_notifications[idx]);
if (size(encoded_notification) < 4)
print("Error: emesary.IncomingBridge.ProcessIncoming bad msg ",encoded_notifications[idx]);
else {
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 {
bridged_notification.FromIncomingBridge = 1;
bridged_notification.Callsign = me.GetCallsign();
2020-03-24 11:02:13 +00:00
if(IncomingMPBridge.trace>1)
print("ProcessIncoming callsign=",bridged_notification.Callsign," ",me.PropertyRoot, " msg_type=",msg_type_id," idx=",msg_idx, " bridge_idx=",bridged_notification.IncomingMessageIndex);
if (msg_idx > bridged_notification.IncomingMessageIndex) {
if(IncomingMPBridge.trace==1)
print("ProcessIncoming callsign=",bridged_notification.Callsign," ",me.PropertyRoot, " msg_type=",msg_type_id," idx=",msg_idx, " bridge_idx=",bridged_notification.IncomingMessageIndex);
2020-07-25 12:11:07 +00:00
var msg_body = encoded_notification[3];
if (IncomingMPBridge.trace > 2)
print("received idx=",msg_idx," ",msg_type_id,":",bridged_notification.NotificationType);
# populate fields
var bridgedProperties = bridged_notification.bridgeProperties();
var msglen = size(msg_body);
if (IncomingMPBridge.trace > 2)
print("Process ",msg_body," len=",msglen, " BPsize = ",size(bridgedProperties));
var pos = 0;
for (var bpi = 0; bpi < size(bridgedProperties); bpi += 1) {
if (pos < msglen) {
if (IncomingMPBridge.trace > 2)
print("dec: pos ",pos);
var bp = bridgedProperties[bpi];
dv = bp.setValue(msg_body, me, pos);
if (IncomingMPBridge.trace > 2)
2020-05-31 03:18:04 +00:00
print(" --> next pos ", dv.pos);
2020-03-24 11:02:13 +00:00
2020-07-25 12:11:07 +00:00
if (dv.pos == pos or dv.pos > msglen)
break;
pos = dv.pos;
2017-11-18 00:51:08 +00:00
} else {
print("Error: emesary.IncomingBridge.ProcessIncoming: [",bridged_notification.NotificationType,"] supplementary encoded value at position ",bpi);
2017-07-23 14:04:46 +00:00
break;
}
2016-04-16 13:14:16 +00:00
}
2017-11-18 00:51:08 +00:00
# maybe extend the bridge to allow certain notifications to only be routed to a specific player;
# i.e.
# (notification.Callsign == nil or notification.Callsign == getprop("/sim/multiplay/callsign"))
2016-04-16 13:14:16 +00:00
if (bridged_notification.Ident == "none")
2017-11-18 00:51:08 +00:00
bridged_notification.Ident = "mp-bridge";
2020-07-25 12:11:07 +00:00
2020-03-24 11:02:13 +00:00
bridged_notification.IncomingMessageIndex = msg_idx;
2020-07-25 12:11:07 +00:00
me.Transmitter.NotifyAll(bridged_notification);
2016-04-16 13:14:16 +00:00
}
2016-04-09 19:39:57 +00:00
}
}
}
}
2017-11-18 00:51:08 +00:00
foreach (var n; new_class.NotificationsToBridge) {
2016-04-12 21:25:11 +00:00
print("IncomingBridge: ",n.NotificationType);
2016-04-09 19:39:57 +00:00
}
return new_class;
2018-10-31 09:05:34 +00:00
},
connectIncomingBridge : func(path, notification_list, mpidx, transmitter, _propertybase){
var incomingBridge = emesary_mp_bridge.IncomingMPBridge.new(path, notification_list, mpidx, transmitter, _propertybase);
incomingBridge.Connect(path~"/");
2020-07-25 12:11:07 +00:00
if (me.incomingBridgeList[path] == nil) {
me.incomingBridgeList[path] = [incomingBridge];
} else {
append(me.incomingBridgeList[path],incomingBridge);
}
2018-10-31 09:05:34 +00:00
return incomingBridge;
},
2016-04-12 21:25:11 +00:00
#
# 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
2020-07-25 12:11:07 +00:00
startMPBridge : func(notification_list, mpidx=19, transmitter=nil, _propertybase="emesary/bridge")
2016-04-09 19:39:57 +00:00
{
2018-10-31 09:05:34 +00:00
me.incomingBridgeList = {};
2016-04-12 21:25:11 +00:00
#
# Create bridge whenever a client connects
#
2016-04-09 19:39:57 +00:00
setlistener("/ai/models/model-added", func(v)
2020-07-25 12:11:07 +00:00
{
# Model added will be eg: /ai[0]/models[0]/multiplayer[0]
var path = v.getValue();
2016-04-12 21:25:11 +00:00
2020-07-25 12:11:07 +00:00
# Ensure we only handle multiplayer elements
if (find("/multiplayer",path) > 0) {
var callsign = getprop(path~"/callsign");
print("Creating Emesary MPBridge for ",path);
if (callsign == "" or callsign == nil)
callsign = path;
2016-04-12 21:25:11 +00:00
2018-10-31 09:05:34 +00:00
me.connectIncomingBridge(path, notification_list, mpidx, transmitter, _propertybase);
2020-07-25 12:11:07 +00:00
}
2016-04-09 19:39:57 +00:00
});
2016-04-12 21:25:11 +00:00
#
# when a client disconnects remove the associated bridge.
#
2016-04-09 19:39:57 +00:00
setlistener("/ai/models/model-removed", func(v){
var path = v.getValue();
2020-07-25 12:11:07 +00:00
var bridges = me.incomingBridgeList[path];
if (bridges != nil) {
foreach(bridge;bridges) {
bridge.Remove();
# print("Bridge removed for ",v.getValue());
2017-07-23 14:04:46 +00:00
}
2020-07-25 12:11:07 +00:00
me.incomingBridgeList[path] = nil;
}
2017-07-23 14:04:46 +00:00
});
2016-04-09 19:39:57 +00:00
},
2020-07-25 12:11:07 +00:00
};