From a13f9b3bb0e0c0ebecbbd3fdf4d793d3f7fdaeec Mon Sep 17 00:00:00 2001 From: Henning Stahlke <github@henningstahlke.de> Date: Mon, 29 Jun 2020 10:49:18 +0200 Subject: [PATCH] emesary.nas add comments, type checks; fix typos; add debug/emesary.deb.nas add fgcommand to use the global transmitter --- Nasal/debug/emesary.deb.nas | 50 +++++ Nasal/emesary.nas | 351 ++++++++++++++++++++++++++---------- Nasal/globals.nas | 67 ++++++- Nasal/notifications.nas | 3 +- defaults.xml | 3 + 5 files changed, 373 insertions(+), 101 deletions(-) create mode 100644 Nasal/debug/emesary.deb.nas diff --git a/Nasal/debug/emesary.deb.nas b/Nasal/debug/emesary.deb.nas new file mode 100644 index 000000000..b237a5d6d --- /dev/null +++ b/Nasal/debug/emesary.deb.nas @@ -0,0 +1,50 @@ +#------------------------------------------------------------------------------- +# emesary.deb.nas - emesary debug helpers +#------------------------------------------------------------------------------- +var _emesaryDebugN = props.getNode("/_debug/emesary/",1); +var _emesaryDebugEnableN = _emesaryDebugN.getNode("enabled",1); +_emesaryDebugEnableN.setBoolValue(_emesaryDebugEnableN.getValue()); + +var __setup = func { + var debugRecipient = emesary.Recipient.new("Debug"); + + debugRecipient.Receive = func(ntf) { + _emesaryDebugEnableN.getValue() or return; + + if (!isa(ntf, emesary.Notification)) { + logprint(DEV_ALERT, "debugRecipient: argument is not a emesary.Notification!"); + return emesary.Transmitter.ReceiptStatus_Fail; + } + # ignore FrameNotification as it would flood the log/console at frame rate + if (ntf.NotificationType != "FrameNotification") { + print("debugRecipient: type=", ntf.NotificationType, " id=", ntf.Ident); + debug.dump(keys(ntf)); + # count notifications + if (isstr(ntf.NotificationType)) { + var cnt = _emesaryDebugN.getChild(ntf.NotificationType, 0, 1); + if (isstr(ntf.Ident)) { + cnt = cnt.getNode(ntf.Ident, 1); + } + if (cnt.getValue() == nil) { + cnt.setIntValue(0); + } + cnt.increment(); + } + } + return emesary.Transmitter.ReceiptStatus_NotProcessed; + } + + emesary.GlobalTransmitter.Register(debugRecipient); + + # send a test message + var debugNotification = emesary.Notification.new("debug", "test"); + emesary.GlobalTransmitter.NotifyAll(debugNotification); + + #add monitoring + emesary._transmitters.addCallback(func (k, v) { + emesary._transmitters.keys2props(_emesaryDebugN); + }); + emesary._transmitters.keys2props(_emesaryDebugN); +} + +settimer(__setup,0); \ No newline at end of file diff --git a/Nasal/emesary.nas b/Nasal/emesary.nas index 0583c138a..35f5621d9 100644 --- a/Nasal/emesary.nas +++ b/Nasal/emesary.nas @@ -22,56 +22,101 @@ # # Copyright © 2016 Richard Harrison Released under GPL V2 # + #--------------------------------------------------------------------------- + # Classes in this file: + # Transmitter + # Notification + # Recipient #---------------------------------------------------------------------------*/ var __emesaryUniqueId = 14; # 0-15 are reserved, this way the global transmitter will be 15. +# add registry so we can find a transmitter by name in genericEmesaryGlobalTransmitterTransmit +var _transmitters = Hash.new("transmitters"); + +var _registerTransmitter = func (key, t) { + _transmitters.set(key, t); +} +var getTransmitter = func (key) { + return _transmitters.get(key); +} + # Transmitters send notifications to all recipients that are registered. var Transmitter = { ReceiptStatus_OK : 0, # Processing completed successfully ReceiptStatus_Fail : 1, # Processing resulted in at least one failure - ReceiptStatus_Abort : 2, # Fatal error, stop processing any further recipieints of this message. Implicitly failed. - ReceiptStatus_Finished : 3, # Definitive completion - do not send message to any further recipieints + ReceiptStatus_Abort : 2, # Fatal error, stop processing any further recipients of this message. Implicitly failed. + ReceiptStatus_Finished : 3, # Definitive completion - do not send message to any further recipients ReceiptStatus_NotProcessed : 4,# Return value when method doesn't process a message. ReceiptStatus_Pending : 5, # Message sent with indeterminate return status as processing underway ReceiptStatus_PendingFinished : 6,# Message definitively handled, status indeterminate. The message will not be sent any further # create a new transmitter. shouldn't need many of these + # _ident: string; name of the transmitter, used in debug messages new: func(_ident) { - var new_class = { parents: [Transmitter]}; - new_class.Recipients = []; - new_class.Ident = _ident; - new_class.Timestamp = nil; - new_class.MaxMilliseconds = 1; + if (!isscalar(_ident)) { + logprint(LOG_ALERT, "Transmitter.new: argument must be a scalar!") + } __emesaryUniqueId += 1; - new_class.UniqueId = __emesaryUniqueId; + var new_class = { + parents : [Transmitter], + Recipients : [], + Ident : _ident, + Timestamp : nil, + MaxMilliseconds : 1, + UniqueId: __emesaryUniqueId, + }; + _registerTransmitter(_ident, new_class); return new_class; }, + OverrunDetection: func(max_ms=0){ - if (max_ms){ - if (me.Timestamp == nil) + if (isnum(max_ms) and max_ms) { + if (me.Timestamp == nil) me.Timestamp = maketimestamp(); - me.MaxMilliseconds = max_ms; -#print("Set overrun detection ",me.Ident, " to ", me.MaxMilliseconds); - } else { - # me.Timestamp = nil; - me.MaxMilliseconds = 0; -#print("Disable overrun detection ",me.Ident); - } - } - , + me.MaxMilliseconds = max_ms; + logprint(LOG_INFO, "Set overrun detection ",me.Ident, " to ", me.MaxMilliseconds); + return 1; + } else { + # me.Timestamp = nil; + me.MaxMilliseconds = 0; + logprint(LOG_INFO, "Disable overrun detection ",me.Ident); + return 0; + } + }, # Add a recipient to receive notifications from this transmitter Register: func (recipient) { + # not inheriting from Recipient is maybe strange but will not crash + if (!isa(recipient, Recipient)) + { + logprint(LOG_INFO, "Transmitter.Register: argument is not a Recipient object"); + } + #not having a Receive function is an error + if (!isfunc(recipient["Receive"])) + { + logprint(DEV_ALERT, "Transmitter.Register: Error, argument has no Receive method!"); + return 0; + } + foreach (var r; me.Recipients) + { + if (r == recipient) { + logprint(DEV_ALERT, "Transmitter.Register: Recipient already registered!"); + return 1; + } + } append(me.Recipients, recipient); + return 1; }, + DeleteAllRecipients: func { me.Recipients = []; }, + # Stops a recipient from receiving notifications from this transmitter. DeRegister: func(todelete_recipient) { @@ -111,10 +156,12 @@ var Transmitter = # - Fail > message not handled. A status of Abort from a recipient will result in our status # being fail as Abort means that the message was not and cannot be handled, and # allows for usages such as access controls. + # message: hash; Notification passed to the Receive() method of registered recipients NotifyAll: func(message) { - if (message == nil){ - print("Emesary: bad notification nil"); + if (!isa(message, Notification)) + { + logprint(DEV_ALERT, "Transmitter.NotifyAll: argument must be a Notification!"); return Transmitter.ReceiptStatus_NotProcessed; } me._return_status = Transmitter.ReceiptStatus_NotProcessed; @@ -134,9 +181,12 @@ var Transmitter = foreach(var line; err) { print(line); } - print("Recipient ",recipient.Ident, " has been removed from transmitter (", me.Ident, ") because of the above error"); + logprint(LOG_ALERT, "Recipient ",recipient.Ident, + " has been removed from transmitter (", me.Ident, + ") because of the above error"); me.DeRegister(recipient); - return Transmitter.ReceiptStatus_Abort;#need to break the foreach due to having modified what its iterating over. + #need to break the foreach due to having modified what its iterating over. + return Transmitter.ReceiptStatus_Abort; } if (me.Timestamp != nil) { recipient.TimeTaken = me.Timestamp.elapsedUSec()/1000.0; @@ -166,20 +216,26 @@ var Transmitter = } elsif(me._rstat == Transmitter.ReceiptStatus_Abort) { + # this is a final results, e.g. no more recipients will be + # notified but the result is returned as NotifyAll result. return Transmitter.ReceiptStatus_Abort; } elsif(me._rstat == Transmitter.ReceiptStatus_Finished) { + # this is a final results, e.g. no more recipients will be + # notified but the result is returned as NotifyAll result. return Transmitter.ReceiptStatus_OK; } } } - if (me.MaxMilliseconds and me.TimeTaken > me.MaxMilliseconds ){ - printf("Overrun: %s ['%s'] %1.2fms max (%d)",me.Ident,message.NotificationType, me.TimeTaken,me.MaxMilliseconds); -# print("Overrun: ",me.Ident, "['",message.NotificationType,"']", " ", me.TimeTaken,"ms (max ",me.MaxMilliseconds," ms)"); + if (me.MaxMilliseconds and me.TimeTaken > me.MaxMilliseconds) { + logprint(LOG_WARN, sprintf("Overrun: %s ['%s'] %1.2fms max (%d)", + me.Ident, message.NotificationType, me.TimeTaken, me.MaxMilliseconds)); foreach (var recipient; me.Recipients) { - if (recipient.TimeTaken) - printf(" -- Recipient %25s %7.2f ms",recipient.Ident, recipient.TimeTaken); + if (recipient.TimeTaken) { + logprint(LOG_WARN, sprintf(" -- Recipient %25s %7.2f ms", + recipient.Ident, recipient.TimeTaken)); + } } } return me._return_status; @@ -222,102 +278,158 @@ var QueuedTransmitter = } }; -# -# -# Base class for Notifications. By convention a Notification has a type and a value. -# 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. -# 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) -# Messages that indicate an event (such as after a pilot action) -# will usually be non-distinct. So an example would be gear/up down -# or ATC acknowledgements that all need to be transmitted + +#--------------------------------------------------------------------------- +# Notification - base class +# By convention a Notification has a type and a value. Derived classes can add +# extra properties or methods. +# +# NotificationType: Notification Type +# Ident: Can be an ident, or for simple messages a value that needs transmitting. +# 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) +# Messages that indicate an event (such as after a pilot action) +# will usually be non-distinct. So an example would be gear/up down +# 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 +# only the most recently sent distinct message will be transmitted over MP. +# Example: +# position update, where only current position is relevant -> IsDistinct=1; +# 0 = queue all messages for MP bridging +# 1 = queue only latest message (replace any old message of same type+ident) +# +var TypeIdUnspecified = 1; var NotificationAutoTypeId = 1; var Notification = { new: func(_type, _ident, _typeid=0) { - var new_class = { parents: [Notification]}; - new_class.Ident = _ident; - new_class.NotificationType = _type; - new_class.IsDistinct = 1; - new_class.FromIncomingBridge = 0; - new_class.Callsign = nil; - - new_class.GetBridgeMessageNotificationTypeKey = func { - return me.NotificationType~"."~me.Ident; - }; - if (_typeid == 0) - { - _typeid = NotificationAutoTypeId; - NotificationAutoTypeId = NotificationAutoTypeId + 1; + if (!isscalar(_type)) { + logprint(DEV_ALERT, "Notification.new: _type must be a scalar!"); + return nil; } - new_class.TypeId = _typeid; + if (!isscalar(_ident)) { + logprint(DEV_ALERT, "Notification.new: _ident is not scalar but ", typeof(_ident)); + return nil; + } + + if (_typeid == 0) { + NotificationAutoTypeId += 1; + # IDs >= 16 are reserved; see http://wiki.flightgear.org/Emesary_Notifications + if (NotificationAutoTypeId == 16) { + logprint(LOG_ALERT, "Notification: AutoTypeID limit exceeded: "~NotificationAutoTypeId); + return nil; + } + _typeid = NotificationAutoTypeId; + } + + var new_class = { + parents: [Notification], + NotificationType: _type, + Ident: _ident, + IsDistinct: 1, #1: MP bridge only latest notification + FromIncomingBridge: 0, + Callsign: nil, + TypeId: _typeid, # used in MP bridged + }; return new_class; }, + + setType: func(_type) { + if (!isscalar(_type)) { + logprint(DEV_ALERT, "Notification.new: _type must be a scalar!"); + return nil; + } + me.NotificationType = _type; + return me; + }, + + setIdent: func(_ident) { + if (!isscalar(_ident)) { + logprint(DEV_ALERT, "Notification.new: _ident is not scalar but ", typeof(_ident)); + return nil; + } + me.Ident = _ident; + return me; + }, + + GetBridgeMessageNotificationTypeKey: func { + return me.NotificationType~"."~me.Ident; + }, }; -# Inherit or implement class with the same signatures to receive messages. +#--------------------------------------------------------------------------- +# Recipient - base class for receiving notifications. +# +# You have to implement the Receive method +# The Receive method must return a sensible ReceiptStatus_* code var Recipient = { new: func(_ident) { - var new_class = { parents: [Recipient]}; if (_ident == nil or _ident == "") { _ident = id(new_class); - print("Emesary Error: Ident required when creating a recipient, defaulting to ",_ident); + logprint(LOG_WARN, "Emesary Error: Ident required when creating a recipient, defaulting to ", _ident); } - Recipient.construct(_ident, new_class); - }, - construct: func(_ident, new_class) - { - new_class.Ident = _ident; - new_class.RecipientActive = 1; __emesaryUniqueId += 1; - new_class.UniqueId = __emesaryUniqueId; - new_class.Receive = func(notification) - { - # warning if required function not - print("Emesary Error: Receive function not implemented in recipient ",me.Ident); - return Transmitter.ReceiptStatus_NotProcessed; + var new_class = { + parents: [Recipient], + Ident: _ident, + RecipientActive: 1, + UniqueId: __emesaryUniqueId, }; return new_class; }, + + Receive: func(notification) + { + logprint(DEV_ALERT, "Emesary Error: Receive function not implemented in recipient ", me.Ident); + return Transmitter.ReceiptStatus_NotProcessed; + }, + + setReceive: func(f) + { + if (isfunc(f)) { me.Receive = f; } + else { logprint(DEV_ALERT, "Recipient.addReceive: argument must be a function!"); } + return me; + }, }; # -# Instantiate a Global Transmitter, this is a convenience and a known starting point. Generally most classes will -# use this transmitters, however other transmitters can be created and merely use the global transmitter to discover each other +# Instantiate a Global Transmitter, this is a convenience and a known starting point. +# Generally most classes will use this transmitters, however other transmitters +# can be created and merely use the global transmitter to discover each other. var GlobalTransmitter = Transmitter.new("GlobalTransmitter"); # # Base method of transferring all numeric based values. # Using the same techinque as base64 - except this is base248 because we can use a much wider range of characters. -# +# var BinaryAsciiTransfer = { - 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), + #excluded chars 32 (<space>), 33 (!), 35 (#), 36($), 126 (~), 127 (<del>) + 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), # base248: powers of 2 (i.e. po2(x) = f(248 ^ x); # 0 based list so the first item is really[1]; i.e. 124 which is 248/2 as po2 is the magnitude excluding sign po2: [1, 124, 30752, 7626496, 1891371008, 469060009984, 116326882476032, 28849066854055936], @@ -356,7 +468,7 @@ var BinaryAsciiTransfer = decodeNumeric : func(str, length, factor, pos) { var irange = int(BinaryAsciiTransfer.po2[length]/factor); - var power = length - 1; + var power = length-1; BinaryAsciiTransfer.retval.value = 0; BinaryAsciiTransfer.retval.pos = pos; @@ -523,6 +635,61 @@ var TransferCoord = return dv; } }; + +# genericEmesaryGlobalTransmitterTransmit allowes to use the emesary.GlobalTransmitter via fgcommand +# which in turn allows using it in XML bindings, e.g. +# <binding> +# <command>emesary-transmit</command> +# <type>cockpit-switch</type> +# <ident>eicas-page-select</ident> +# <page>hydraulic</page> +# </binding> +# +var genericEmesaryGlobalTransmitterTransmit = func(node) +{ + var transmitter = emesary.GlobalTransmitter; + var t = node.getNode("transmitter",1).getValue(); + if (t != nil) { + transmitter = emesary.getTransmitter(t); + if (transmitter == nil) { + logprint(LOG_WARN, "Invalid transmitter "~t); + return; + } + } + var type = node.getNode("type").getValue(); + if (type == nil) { + logprint(LOG_WARN, "emesary-transmit requires a type"); + return; + } + var ident = node.getNode("ident").getValue(); + if (ident == nil) { + logprint(LOG_WARN, "emesary-transmit requires an ident"); + return; + } + var typeid = node.getNode("typeid",1).getValue() or 0; + if (typeid == 0) { + typeid = TypeIdUnspecified; + logprint(LOG_WARN, "emesary-transmit using generic typeid ", typeid); + } + + var message = emesary.Notification.new(type, ident, typeid); + node.removeChild("type"); + node.removeChild("id"); + node.removeChild("typeid"); + + # add remaining nodes to the message hash + var children = node.getValues(); + if (children != nil) { + foreach (var key; keys(children)) { + message[key] = children[key]; + } + } + transmitter.NotifyAll(message); +}; + +removecommand("emesary-transmit"); #in case of reload +addcommand("emesary-transmit", genericEmesaryGlobalTransmitterTransmit); + #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); diff --git a/Nasal/globals.nas b/Nasal/globals.nas index 895156ecc..64e6922ca 100644 --- a/Nasal/globals.nas +++ b/Nasal/globals.nas @@ -41,10 +41,9 @@ var assert = func (condition, message=nil) { # (class) object. Example: isa(someObject, props.Node) # var isa = func(obj, class) { - if(ishash(obj) and obj["parents"] != nil) { + if (ishash(obj) and obj["parents"] != nil) { foreach(var c; obj.parents) { - if(c == class or isa(c, class)) - return 1; + if (c == class or isa(c, class)) return 1; } } return 0; @@ -58,9 +57,8 @@ var isa = func(obj, class) { # tree. # var fgcommand = func(cmd, node=nil) { - if(isa(node, props.Node)) node = node._g; - elsif(ishash(node)) - node = props.Node.new(node)._g; + if (isa(node, props.Node)) node = node._g; + elsif (ishash(node)) node = props.Node.new(node)._g; _fgcommand(cmd, node); } @@ -97,8 +95,9 @@ var abs = func(v) { return v < 0 ? -v : v } # 0 to wrap the interpolated value properly. # var interpolate = func(node, val...) { - if (isa(node, props.Node)) node = node._g; - elsif (!isscalar(node) and !isghost(node)) + if (isa(node, props.Node)) + node = node._g; + elsif (!isscalar(node) and !isghost(node)) die("bad argument to interpolate()"); _interpolate(node, val); } @@ -189,3 +188,55 @@ settimer(func { if(size(file) > 4 and substr(file, -4) == ".nas") io.load_nasal(path ~ "/" ~ file, substr(file, 0, size(file) - 4)); }, 0); + +# simple hash class for developers, allows to add callback on write +Hash = { + class_name: "Hash", + + new: func(name) { + var obj = { + parents: [me], + name: name, + _h: {}, + _callback: func, + }; + return obj; + }, + + set: func (key, value) { + me._h[key] = value; + me._callback(key, value); + return me; + }, + + get: func (key) { + return me._h[key]; + }, + + getName: func (key) { + return me.name; + }, + + getKeys: func () { + return keys(me._h); + }, + + keys2props: func (p) { + if (!isa(p, props.Node)) { + p = props.getNode(p,1); + } + p = p.getNode(me.name,1); + foreach (var key; keys(me._h)) { + p.getNode(key,1); + } + return; + }, + + # callback for set() + addCallback: func (f) { + if (isfunc(f)) { + me._callback = f; + } + return me; + }, +}; diff --git a/Nasal/notifications.nas b/Nasal/notifications.nas index 271c876f2..5ff867ad8 100644 --- a/Nasal/notifications.nas +++ b/Nasal/notifications.nas @@ -4,7 +4,7 @@ # # File Type : Implementation File # - # Description : Messages that are applicable across all models and do not specifically relate to a single sysmte + # Description : Messages that are applicable across all models and do not specifically relate to a single system # : - mostly needed when using the mutiplayer bridge # # Author : Richard Harrison (richard@zaretto.com) @@ -27,6 +27,7 @@ var AircraftControlNotification_Id = 17; var GeoEventNotification_Id = 18; # event ID 19 reserved for armaments and stores (model defined). var PFDEventNotification_Id = 20; +var ArmamentInFlightNotification = 21; # # PropertySyncNotificationBase is a wrapper class for allow properties to be synchronized between diff --git a/defaults.xml b/defaults.xml index e4a9b33f4..87b79325a 100644 --- a/defaults.xml +++ b/defaults.xml @@ -1383,6 +1383,9 @@ Started September 2000 by David Megginson, david@megginson.com <console> <enabled type="bool">true</enabled> </console> + <debug> + <enabled type="bool">false</enabled> + </debug> <input_helpers> <enabled type="bool">true</enabled> </input_helpers>