diff --git a/Aircraft/Generic/an_spn_46.nas b/Aircraft/Generic/an_spn_46.nas new file mode 100644 index 000000000..537f857d3 --- /dev/null +++ b/Aircraft/Generic/an_spn_46.nas @@ -0,0 +1,330 @@ +#--------------------------------------------------------------------------- +# +# Title : AN/SPN-46 Precision Approach Landing System (PALS) +# +# File Type : Implementation File +# +# Description : Representative emulation of the functions of the AN/SPN-46 using emesary +# : Where an AN/SPN-46 is required it is sufficient to instantiate a ANSPN46_System connected to the instantiator model. +# : Register with an instance of a Transmitter, and provide a Receive method, periodically send out Active messages +# : expecting ActiveResponse messages which may result in a Communication. +# : This is implemented as a set of messages that models the operation of the PALS +# +# References : http://trace.tennessee.edu/cgi/viewcontent.cgi?article=3297&context=utk_gradthes +# : http://www.navair.navy.mil/index.cfm?fuseaction=home.displayPlatform&key=E8D18768-14B6-4CF5-BAB5-12B009070CFC +# : http://www.afceaboston.com/documents/events/cnsatm2010/Briefs/4%20-%20Friday/03-Navy_Landing_Systems_Roadmap-(CDR%20Easler).pdf +# +# Author : Richard Harrison (richard@zaretto.com) +# +# Creation Date : 29 January 2016 +# +# Version : 4.8 +# +# Copyright � 2016 Richard Harrison Released under GPL V2 +# +#---------------------------------------------------------------------------*/ +#Message Reference: +#---------------------------------------------------------------------------*/ +# Notification 1 ANSPN46ActiveNotification from carrier to aircraft +# Transmitted via GlobalTransmitter at 1hz +# - carrier position +# - beam start +# - beam angle +# - channel / frequency information +# - beam range / power +# +# Notification 2 - from aircraft to carrier +# - aircraft position +# - radar return size +# - aircraft altitude, heading, velocity +# - respose indicating tuned or not +# +# Notification 3 - from carrier to aircraft +# - lateral deviation +# - vertical deviation +# - LSO information / messages +# - system status +# - lateral +# +#Operation: +#--------------------- +# The ANSPN64 system will send periodically send out a ANSPN46ActiveNotification +# the rest of the logic within this system related to aircraft will be handled when +# the ANSPN46ActiveResponseNotification - which itself in turn will send out a ANSPN46CommunicationNotification +#---------------------- +# NOTE: to avoid garbage collection all of the notifications that are sent out are created during construction +# and simply modified prior to sending. This works because emesary is synchronous and therefore the state +# and lifetime are known. + +# +# Notification(1) from carrier to any aircraft within range. +# +var ANSPN46ActiveNotification = +{ + # Create a new active notification notification. Aircraft will respond to this. + # param(_anspn46_system): instance of ANSPN46_System which will send the notification + new: func(_anspn46_system) + { + var new_class = emesary.Notification.new("ANSPN46ActiveNotification", _anspn46_system.Ident); + new_class.ANSPN46_system = _anspn46_system; + + # + # Set notification properties from the ANSPN46_System. + new_class.set_from = func(_anspn) + { + me.Position = _anspn.GetCarrierPosition(); + me.BeamPosition = _anspn.GetTDZPosition(); + me.BeamAngle = 35; + me.Channel = _anspn.GetChannel(); + me.BeamRange = 35; ##nm + me.BeamPower = 999; ## mw ??? + }; + new_class.set_from(_anspn46_system); + return new_class; + }, +}; + +# Notification(2) - from aircraft to carrier sent in response to Notification(1) above +# +var ANSPN46ActiveResponseNotification = +{ + new: func(_ident) + { + var new_class = emesary.Notification.new("ANSPN46ActiveResponseNotification", _ident); + new_class.Respond = func(_msg) + { + new_class.Position = geo.aircraft_position(); + new_class.Heading = getprop("orientation/heading-deg"); + new_class.RadarReturnStrength = 1; # normalised value based on RCS beam power etc. + new_class.Tuned = 0; # 0 or 1 + new_class.ufps = getprop("velocities/uBody-fps"); + return me; + } + return new_class; + }, +}; + +# Notification 3 - from carrier to aircraft as a result of active response notification +# - only sent if the aircraft is set to the same channel that we are transmitting on +# +var ANSPN46CommunicationNotification = +{ + new: func(_ident, _anspn46_system) + { + var new_class = emesary.Notification.new("ANSPN46CommunicationNotification", _ident); + new_class.Model = _anspn46_system.Model; + + new_class.set_from = func(_ident, _msg, _anspn46_system) + { + var carrier_ara_63_position = _anspn46_system.GetCarrierPosition(); + var carrier_heading = _anspn46_system.GetCarrierHeading(); + var carrier_ara_63_heading = 0; + +# relative offset of the course to the tdz +# according to my measurements the Nimitz class is 8.1362114 degrees (measured 178 vs carrier 200 allowing for local magvar -13.8637886) +# i.e. this value is from tuning rather than calculation + + if (carrier_heading != nil) + carrier_ara_63_heading = carrier_heading.getValue() - 8.1362114; + + var range = _msg.Position.distance_to(carrier_ara_63_position); + var bearing_to = _msg.Position.course_to(carrier_ara_63_position); + var deviation = bearing_to - carrier_ara_63_heading; + + deviation = deviation *0.1; + me.ReturnPosition = _msg.Position; + me.ReturnBearing = getprop("orientation/heading-deg"); + +# the AN/SPN 46 has a 20nm range with a 3 degree beam; ref: F14-AAD-1 17.3.2 + if (range < 37000 and abs(deviation) < 3) + { + var FD_TAN3DEG = math.tan(3.0 / 57.29577950560105); + var deck_height=20; + var gs_height = ((range*FD_TAN3DEG)) + deck_height; + var gs_deviation = (gs_height - _msg.Position.alt()) / 42.0; + + if (gs_deviation > 1) + gs_deviation = 1; + else if (gs_deviation < -1) + gs_deviation = -1; + +# if not in range message will not be transmitted. + me.InRange = 1; + +# calculate the deviation from the ideal approach + me.VerticalAdjustmentCommanded = gs_deviation; + me.HorizontalAdjustmentCommanded = deviation; + + me.LateralDeviation = deviation; + me.VerticalDeviation = gs_deviation; + + me.Distance = range; + + me.SignalQualityNorm = 1; + +# +# work out the rough ETA for the 10 seconds light, and use this +# to decide whether or not to waveoff + var eta = range / (_msg.ufps / 3.281); + + if(eta <= 10 and range < 800 and range > 150) + { + me.TenSeconds = 1; + if(math.abs(deviation) > 0.2 or math.abs(gs_deviation) > 0.2) + { + me.WaveOff = 1; + } + else + { + me.WaveOff = 0; + } + } + else + { + me.TenSeconds = 0; + me.WaveOff = 0; + } + me.LSOMessage = ""; + me.SystemStatus = "OK"; # Wave off, error, etcc. + } + else + { +# +# No response will be sent when not in range; so ensure values are all cleared. + me.InRange = 0; + me.VerticalAdjustmentCommanded = 0; + me.HorizontalAdjustmentCommanded = 0; + me.SignalQualityNorm =0; + me.Distance = -10000000000; + me.TenSeconds = 0; + me.WaveOff = 0; + me.InRange = 0; + me.VerticalDeviation = 0; + me.LateralDeviation = 0; + me.LSOMessage = ""; + me.SystemStatus = ""; + } + }; + return new_class; + }, +}; + +# +# The main AN/SPN46 system implemented using emesary. +# Periodically the Update method should be called, which will +# send out a notification via the global transmitter so that aircraft within range +# can respond. This is similar to a radar transmit and return. +# There should be an instance of this class created in the nasal section of the model xml +# Once an aircraft is within range it will receive guidance that can be displayed. +# It is the responsibility of the AN/SPN system to decided whether a craft is within range. +# It is the responsibility of the aircraft receiver to indicate whether it is tuned in or not +# if it the aircraft is not tuned into the right channel the receiver (e.g. ARA-63) will not receive anything; however +# the AN/SPN system (being a radar) will still have guidance information that could be relayed over the +# comms channel or displayed on a radar display on the carrier. +# +var ANSPN46_System = +{ + new: func(_ident,_model) + { + print("AN/SNP46 created for "~_ident); + + var new_class = emesary.Recipient.new("ANSPN46_System "~_ident); + + new_class.ara_63_position = geo.Coord.new(); + new_class.Model = _model; + new_class.communicationNotification = ANSPN46CommunicationNotification.new(new_class.Ident, new_class); + new_class.Channel = 2; + new_class.UpdateRate = 10; + +#------------------------------------------- +# Receive override: +# Iinterested in receiving ANSPN46ActiveResponseNotification. When we get +# one of these we can respond with a CommunicationNotification + + new_class.Receive = func(notification) + { + if (notification.Type == "ANSPN46ActiveResponseNotification") + { + if (notification.Tuned) + { + me.communicationNotification.set_from(me.Ident, notification, me); + if(me.communicationNotification.InRange) + { + me.UpdateRate = 0.2; + emesary.GlobalTransmitter.NotifyAll(me.communicationNotification); + } + } + return emesary.Transmitter.ReceiptStatus_OK; + } + return emesary.Transmitter.ReceiptStatus_NotProcessed; + } +# +# Interface methods +#----------------------------- +# Required interface to get the current carrier position + new_class.GetCarrierPosition = func() + { + var x = me.Model.getNode("position/global-x").getValue() + 88.7713542; + var y = me.Model.getNode("position/global-y").getValue() + 18.74631309; + var z = me.Model.getNode("position/global-z").getValue() + 115.6574875; + me.ara_63_position.set_xyz(x, y, z); + return me.ara_63_position; + }; +# +# Interface to get the carrier heading + new_class.GetCarrierHeading = func() + { + return me.Model.getNode("orientation/true-heading-deg"); + }; +# +# offset of the TDZ (wires) from the carrier centre + new_class.GetTDZPosition = func + { + return me.ara_63_position; + }; +# +# radar beam angle + new_class.GetUpdateRate = func + { + return me.UpdateRate; + }; + new_class.BeamAngle = func + { + return 30; + }; +# +# currently transmitting channel number. + new_class.GetChannel = func + { + return me.Channel; + }; + new_class.SetChannel = func(v) + { + me.Channel = v; + }; +# +# main entry point. The object itself will manage the update rate - but it is +# up to the caller to use this rate + new_class.Update = func + { + # fill in properties of message + me.msg.set_from(me); + + # + # manage the update rate; increase each frame until we get to 10 seconds + # this will be reset if we receive something back from the aircraft. + if (me.UpdateRate < 10) + me.UpdateRate = me.UpdateRate+1; +# print("AN/SPN 46 : update from",me.Ident," rate=",me.UpdateRate); + return emesary.GlobalTransmitter.NotifyAll(me.msg); + }; + +# +# create the message that will be used to notify of an active carrier. This needs to be done after the methods +# have been created as it references them. Implemented like this to reduce garbage collection + new_class.msg = ANSPN46ActiveNotification.new(new_class); + + emesary.GlobalTransmitter.Register(new_class); + return new_class; + }, +} diff --git a/Models/Geometry/Clemenceau/Clemenceau.xml b/Models/Geometry/Clemenceau/Clemenceau.xml index fe05b6b37..fca66b961 100644 --- a/Models/Geometry/Clemenceau/Clemenceau.xml +++ b/Models/Geometry/Clemenceau/Clemenceau.xml @@ -13,6 +13,32 @@ <PropertyList> <path>clem-superstructure.ac</path> + + <nasal> + <load> + <![CDATA[ + # add AN/SPN-46 see http://chateau-logic.com/content/emesary-nasal-implementation-flightgear + var self = cmdarg(); + + fn_an_spn_46 = getprop("/sim/fg-root") ~ "/Aircraft/Generic/an_spn_46.nas"; + io.load_nasal(fn_an_spn_46, "an_spn_46"); + var anspn = an_spn_46.ANSPN46_System.new("Clemenceau", self); + anspn.SetChannel(2); + var an_spn_46_timer = maketimer(6, func { + anspn.Update(); + an_spn_46_timer.restart(anspn.GetUpdateRate()); + }); + an_spn_46_timer.restart(6); + ]]> + </load> + + <unload> + <![CDATA[ + an_spn_46_timer.stop(); + ]]> + </unload> + </nasal> + <texture-path>Textures</texture-path> <offsets> <z-m>0</z-m> diff --git a/Models/Geometry/Nimitz/eisenhower.xml b/Models/Geometry/Nimitz/eisenhower.xml index a625091aa..5e5ab1790 100644 --- a/Models/Geometry/Nimitz/eisenhower.xml +++ b/Models/Geometry/Nimitz/eisenhower.xml @@ -8,7 +8,32 @@ apart from most of the very excellent textures, remain. --> <status>early-production</status> <path>nimitz.ac</path> - <!-- crew to be added later + <nasal> + <load> + <![CDATA[ + + var self = cmdarg(); + print("Model load Nimitz ", self.getPath()); + + fn_an_spn_46 = getprop("/sim/fg-root") ~ "/Aircraft/Generic/an_spn_46.nas"; + io.load_nasal(fn_an_spn_46, "an_spn_46"); + var anspn = an_spn_46.ANSPN46_System.new("Eisenhower", self); + anspn.SetChannel(2); + var an_spn_46_timer = maketimer(6, func { + anspn.Update(); + an_spn_46_timer.restart(anspn.GetUpdateRate()); + }); + an_spn_46_timer.restart(6); + ]]> + </load> + + <unload> + <![CDATA[ + an_spn_46_timer.stop(); + ]]> + </unload> + </nasal> + <!-- crew to be added later <model> <path>Models/Geometry/Nimitz/Crew/crew.xml</path> diff --git a/Models/Geometry/Nimitz/nimitz.xml b/Models/Geometry/Nimitz/nimitz.xml index db63b2ff0..8b2071632 100644 --- a/Models/Geometry/Nimitz/nimitz.xml +++ b/Models/Geometry/Nimitz/nimitz.xml @@ -8,7 +8,32 @@ apart from most of the very excellent textures, remain. --> <status>early-production</status> <path>nimitz.ac</path> - <!-- crew to be added later + <nasal> + <load> + <![CDATA[ + + var self = cmdarg(); + print("Model load Nimitz ", self.getPath()); + + fn_an_spn_46 = getprop("/sim/fg-root") ~ "/Aircraft/Generic/an_spn_46.nas"; + io.load_nasal(fn_an_spn_46, "an_spn_46"); + var anspn = an_spn_46.ANSPN46_System.new("Nimitz", self); + anspn.SetChannel(2); + var an_spn_46_timer = maketimer(6, func { + anspn.Update(); + an_spn_46_timer.restart(anspn.GetUpdateRate()); + }); + an_spn_46_timer.restart(6); + ]]> + </load> + + <unload> + <![CDATA[ + an_spn_46_timer.stop(); + ]]> + </unload> + </nasal> + <!-- crew to be added later <model> <path>Models/Geometry/Nimitz/Crew/crew.xml</path> </model> diff --git a/Models/Geometry/Nimitz/vinson.xml b/Models/Geometry/Nimitz/vinson.xml index ec74004b4..6aead3d7f 100644 --- a/Models/Geometry/Nimitz/vinson.xml +++ b/Models/Geometry/Nimitz/vinson.xml @@ -10,91 +10,104 @@ apart from most of the very excellent textures, remain. --> <nasal> <load> - print("LOAD Vinson ", cmdarg().getPath()); + <![CDATA[ + print("LOAD Vinson ", cmdarg().getPath()); - var fg_root = getprop("/sim/fg-root"); - var self = cmdarg(); - var control_node = self.getNode("controls/turn-to-base-course", 1); - var pos_node = self.getNode("sim/antenna-pos-norm", 1); - pos_node.setDoubleValue(0); - var turn_old = 1; + var fg_root = getprop("/sim/fg-root"); + var self = cmdarg(); + var control_node = self.getNode("controls/turn-to-base-course", 1); + var pos_node = self.getNode("sim/antenna-pos-norm", 1); + pos_node.setDoubleValue(0); + var turn_old = 1; - ######## - # properties used to handle rendering, lighting and to control the Pack-Park - # Due to a change in MPcarrier system ? + # add AN/SPN-46 see http://chateau-logic.com/content/emesary-nasal-implementation-flightgear + fn_an_spn_46 = getprop("/sim/fg-root") ~ "/Aircraft/Generic/an_spn_46.nas"; + io.load_nasal(fn_an_spn_46, "an_spn_46"); + var anspn = an_spn_46.ANSPN46_System.new("Eisenhower", self); + anspn.SetChannel(2); + # not using the main update as need to keep the scheduling that an_spn_46 requests. + var an_spn_46_timer = maketimer(6, func { + anspn.Update(); + an_spn_46_timer.restart(anspn.GetUpdateRate()); + }); + an_spn_46_timer.restart(6); + + ######## + # properties used to handle rendering, lighting and to control the Pack-Park + # Due to a change in MPcarrier system ? - var rembrandt_node = self.getNode("sim/rendering/rembrandt/enabled", 1); - var sunAngleRad_node = self.getNode("sim/time/sun-angle-rad", 1); - var deckPark_node = self.getNode("sim/current-view/deck-park",1); + var rembrandt_node = self.getNode("sim/rendering/rembrandt/enabled", 1); + var sunAngleRad_node = self.getNode("sim/time/sun-angle-rad", 1); + var deckPark_node = self.getNode("sim/current-view/deck-park",1); - ######## - # properties used to calculate rel wind for the bow-wave shader + ######## + # properties used to calculate rel wind for the bow-wave shader - var speed_Node = self.getNode("velocities/speed-kts", 1); - var hdg_Node = self.getNode("orientation/true-heading-deg", 1); - var wind_speed_Node = self.getNode("environment/rel-wind-speed-kts", 1); - wind_speed_Node.setDoubleValue(0); + var speed_Node = self.getNode("velocities/speed-kts", 1); + var hdg_Node = self.getNode("orientation/true-heading-deg", 1); + var wind_speed_Node = self.getNode("environment/rel-wind-speed-kts", 1); + wind_speed_Node.setDoubleValue(0); - ######## - # properties to control the E2C + ######## + # properties to control the E2C - var gear0_Node = self.getNode("gear/gear[0]/position-norm", 1); - gear0_Node.setDoubleValue(1); + var gear0_Node = self.getNode("gear/gear[0]/position-norm", 1); + gear0_Node.setDoubleValue(1); - var gear1_Node = self.getNode("gear/gear[1]/position-norm", 1); - gear1_Node.setDoubleValue(1); + var gear1_Node = self.getNode("gear/gear[1]/position-norm", 1); + gear1_Node.setDoubleValue(1); - var gear2_Node = self.getNode("gear/gear[2]/position-norm", 1); - gear2_Node.setDoubleValue(1); + var gear2_Node = self.getNode("gear/gear[2]/position-norm", 1); + gear2_Node.setDoubleValue(1); - var compression0_Node = self.getNode("gear/gear[0]/compression-norm", 1); - compression0_Node.setDoubleValue(0); + var compression0_Node = self.getNode("gear/gear[0]/compression-norm", 1); + compression0_Node.setDoubleValue(0); - var compression1_Node = self.getNode("gear/gear[1]/compression-norm", 1); - compression1_Node.setDoubleValue(0.9); + var compression1_Node = self.getNode("gear/gear[1]/compression-norm", 1); + compression1_Node.setDoubleValue(0.9); - var compression2_Node = self.getNode("gear/gear[2]/compression-norm", 1); - compression2_Node.setDoubleValue(0.9); + var compression2_Node = self.getNode("gear/gear[2]/compression-norm", 1); + compression2_Node.setDoubleValue(0.9); - var wingfold_Node = self.getNode("surface-positions/wing-fold-pos-norm", 1); - wingfold_Node.setDoubleValue(1.0); + var wingfold_Node = self.getNode("surface-positions/wing-fold-pos-norm", 1); + wingfold_Node.setDoubleValue(1.0); - var rpm0_Node = self.getNode("engines/engine[0]/rpm", 1); - rpm0_Node.setDoubleValue(0.15); + var rpm0_Node = self.getNode("engines/engine[0]/rpm", 1); + rpm0_Node.setDoubleValue(0.15); - var rpm1_Node = self.getNode("engines/engine[1]/rpm", 1); - rpm1_Node.setDoubleValue(0.17); + var rpm1_Node = self.getNode("engines/engine[1]/rpm", 1); + rpm1_Node.setDoubleValue(0.17); - ######## - # the main loop + ######## + # the main loop - var update = func { - var turn = control_node.getValue(); - var value = wind_speed_Node.getValue(); - setprop("/environment/Vinson/rel-wind-speed-kts", value); - value = speed_Node.getValue(); - setprop("/environment/Vinson/spd-kt", value); - value = hdg_Node.getValue(); - setprop("/environment/Vinson/hdg-deg", value); + var update = func { + var turn = control_node.getValue(); + var value = wind_speed_Node.getValue(); + setprop("/environment/Vinson/rel-wind-speed-kts", value); + value = speed_Node.getValue(); + setprop("/environment/Vinson/spd-kt", value); + value = hdg_Node.getValue(); + setprop("/environment/Vinson/hdg-deg", value); - value = getprop("/sim/rendering/rembrandt/enabled"); - rembrandt_node.setBoolValue(value); - value = getprop("/sim/current-view/deck-park"); - deckPark_node.setBoolValue(value); - value = getprop("/sim/time/sun-angle-rad"); - sunAngleRad_node.setValue(value); + value = getprop("/sim/rendering/rembrandt/enabled"); + rembrandt_node.setBoolValue(value); + value = getprop("/sim/current-view/deck-park"); + deckPark_node.setBoolValue(value); + value = getprop("/sim/time/sun-angle-rad"); + sunAngleRad_node.setValue(value); - if (turn_old != turn){ - turn_old = turn; - move_whips(); - } + if (turn_old != turn){ + turn_old = turn; + move_whips(); + } - settimer(update,0); - } + settimer(update,0); + } - var move_whips = func { - var whip_pos = pos_node.getValue(); - if (whip_pos <=1 and whip_pos >=0 and turn_old == 0){ + var move_whips = func { + var whip_pos = pos_node.getValue(); + if (whip_pos <=1 and whip_pos >=0 and turn_old == 0){ whip_pos += 0.001; if (whip_pos > 1) @@ -119,13 +132,16 @@ apart from most of the very excellent textures, remain. --> ######## #start the main loop update(); - - </load> + ]]> + </load> </nasal> <unload> - #print("UNLOAD Vinson ", cmdarg().getPath()); - </unload> + <![CDATA[ + #print("UNLOAD Vinson ", cmdarg().getPath()); + an_spn_46_timer.stop(); + ]]> + </unload> <!-- crew to be added later <model> diff --git a/Nasal/emesary.nas b/Nasal/emesary.nas new file mode 100644 index 000000000..82ed98aa2 --- /dev/null +++ b/Nasal/emesary.nas @@ -0,0 +1,191 @@ + #--------------------------------------------------------------------------- + # + # Title : EMESARY inter-object communication + # + # File Type : Implementation File + # + # Description : Provides generic inter-object communication. For an object to receive a message it + # : must first register with an instance of a Transmitter, and provide a Receive method + # + # : 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://chateau-logic.com/content/emesary-efficient-inter-object-communication-using-interfaces-and-inheritance + # : http://chateau-logic.com/content/c-wpf-application-plumbing-using-emesary + # + # Author : Richard Harrison (richard@zaretto.com) + # + # Creation Date : 29 January 2016 + # + # Version : 4.8 + # + # Copyright � 2016 Richard Harrison Released under GPL V2 + # + #---------------------------------------------------------------------------*/ + +# 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_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 + new: func(_ident) + { + var new_class = { parents: [Transmitter]}; + new_class.Recipients = []; + new_class.Ident = _ident; + return new_class; + }, + + # Add a recipient to receive notifications from this transmitter + Register: func (recipient) + { + append(me.Recipients, recipient); + }, + + # Stops a recipient from receiving notifications from this transmitter. + DeRegister: func(todelete_recipient) + { + var out_idx = 0; + var element_deleted = 0; + + for (var idx = 0; idx < size(me.Recipients); idx += 1) + { + if (me.Recipients[idx] != todelete_recipient) + { + me.Recipients[out_idx] = me.Recipients[idx]; + out_idx = out_idx + 1; + } + else + element_deleted = 1; + } + + if (element_deleted) + pop(me.Recipients); + }, + + RecipientCount: func + { + return size(me.Recipients); + }, + + PrintRecipients: func + { + print("Recpient list"); + for (var idx = 0; idx < size(me.Recipients); idx += 1) + print("Recpient ",idx," ",me.Recipients[idx].Ident); + }, + + # Notify all registered recipients. Stop when receipt status of abort or finished are received. + # The receipt status from this method will be + # - OK > message handled + # - 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. + NotifyAll: func(message) + { + var return_status = Transmitter.ReceiptStatus_NotProcessed; + foreach (var recipient; me.Recipients) + { + if (recipient.Active) + { + var rstat = recipient.Receive(message); + + if(rstat == Transmitter.ReceiptStatus_Fail) + { + return_status = Transmitter.ReceiptStatus_Fail; + } + elsif(rstat == Transmitter.ReceiptStatus_Pending) + { + return_status = Transmitter.ReceiptStatus_Pending; + } + elsif(rstat == Transmitter.ReceiptStatus_PendingFinished) + { + return rstat; + } +# elsif(rstat == Transmitter.ReceiptStatus_NotProcessed) +# { +# ; +# } + elsif(rstat == Transmitter.ReceiptStatus_OK) + { + if (return_status == Transmitter.ReceiptStatus_NotProcessed) + return_status = rstat; + } + elsif(rstat == Transmitter.ReceiptStatus_Abort) + { + return Transmitter.ReceiptStatus_Abort; + } + elsif(rstat == Transmitter.ReceiptStatus_Finished) + { + return Transmitter.ReceiptStatus_OK; + } + } + } + return return_status; + }, + + # Returns true if a return value from NotifyAll is to be considered a failure. + IsFailed: func(receiptStatus) + { + # Failed is either Fail or Abort. + # NotProcessed isn't a failure because it hasn't been processed. + if (receiptStatus == Transmitter.ReceiptStatus_Fail or receiptStatus == Transmitter.ReceiptStatus_Abort) + return 1; + return 0; + } +}; + +# +# +# Base class for Notifications. By convention a Notification has a type and a value. +# SubClasses can add extra properties or methods. +var Notification = +{ + new: func(_type, _value) + { + var new_class = { parents: [Notification]}; + new_class.Value = _value; + new_class.Type = _type; + return new_class; + }, +}; + +# Inherit or implement class with the same signatures to receive messages. +var Recipient = +{ + new: func(_ident) + { + var new_class = { parents: [Recipient]}; + if (_ident == nil or _ident == "") + { + _ident = id(new_class); + print("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.Active = 1; + 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; + }; + return new_class; + }, +}; + +# +# 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");