From 153d42f740bd1c139d939998280c0a4c5736bc5c Mon Sep 17 00:00:00 2001 From: Noiredd Date: Sun, 21 Aug 2022 23:05:40 +0200 Subject: [PATCH] events: factor out all event handling into a separate module --- kOS/pegas.ks | 9 +- kOS/pegas_events.ks | 299 ++++++++++++++++++++++++++++++++++++++++++++ kOS/pegas_util.ks | 269 ++++----------------------------------- 3 files changed, 326 insertions(+), 251 deletions(-) create mode 100644 kOS/pegas_events.ks diff --git a/kOS/pegas.ks b/kOS/pegas.ks index ea35762..155422b 100644 --- a/kOS/pegas.ks +++ b/kOS/pegas.ks @@ -10,6 +10,7 @@ IF cserVersion = "new" { } ELSE { RUN pegas_cser. } +RUN pegas_events. RUN pegas_upfg. RUN pegas_util. RUN pegas_misc. @@ -22,7 +23,7 @@ SET CONFIG:IPU TO kOS_IPU. // Initialize global flags and constants GLOBAL upfgStage IS -1. // System initializes at passive guidance GLOBAL stageEndTime IS TIME. // For Tgo calculation during active guidance (global time) -GLOBAL userEventPointer IS -1. // Index of the last executed userEvent (-1 means none yet) +GLOBAL eventPointer IS -1. // Index of the last executed event (-1 means none yet) GLOBAL commsEventFlag IS FALSE. GLOBAL throttleSetting IS 1. // This is what actually controls the throttle, GLOBAL throttleDisplay IS 1. // and this is what to display on the GUI - see throttleControl() for details. @@ -74,7 +75,7 @@ UNTIL ABORT { // User hooks callHooks("passivePre"). // Sequence handling - userEventHandler(). + eventHandler(). IF commsEventFlag = TRUE { commsEventHandler(). } // Control handling IF ascentFlag = 0 { @@ -140,8 +141,8 @@ callHooks("activeInit"). UNTIL ABORT { // User hooks callHooks("activePre"). - // Sequence handling - userEventHandler(). + // Event handling + eventHandler(). IF commsEventFlag = TRUE { commsEventHandler(). } // Update UPFG target and vehicle state SET upfgTarget["normal"] TO targetNormal(mission["inclination"], mission["LAN"]). diff --git a/kOS/pegas_events.ks b/kOS/pegas_events.ks new file mode 100644 index 0000000..39b28ea --- /dev/null +++ b/kOS/pegas_events.ks @@ -0,0 +1,299 @@ +// Event handling library. + +// Utility to insert an event into the sequence by time order +FUNCTION insertEvent { + // Expects a global variable "sequence" as list of lexicons. + DECLARE PARAMETER event. // Expects a lexicon + + LOCAL eventTime IS event["time"]. + // Go over the list to find the index of insertion... + LOCAL index IS 0. + FOR this IN sequence { + IF eventTime < this["time"] { + // This will cause insertion of the new item AFTER any other item with exact same timing + BREAK. + } + SET index TO index + 1. + } + // ...and insert there. + sequence:INSERT(index, event). +} + +// Create sequence entries for staging events +FUNCTION spawnStagingEvents { + // For each active stage we have to schedule the preStage event and the staging event - except the FIRST ONE which is already + // preStaged by now and we just need to stage it (which will possibly be a no-op if it's a sustainer). We'll iterate over the + // vehicle, computing (cumulatively) burnout times for each stage and spawning events. We know exactly when everything starts: + // `controls["upfgActivation"]`. + // Expects global variables: + // "controls" as lexicon + // "vehicle" as list + // "stagingKillRotTime" as scalar + LOCAL stageActivationTime IS controls["upfgActivation"]. + LOCAL vehicleIterator IS vehicle:ITERATOR. + // The first active stage is already pre-staged so we only need to create the staging event + vehicleIterator:NEXT. + LOCAL stagingEvent IS LEXICON( + "time", stageActivationTime, + "type", "_upfgstage", + "isVirtual", vehicleIterator:VALUE["isVirtualStage"], + "message", "active guidance on" // todo: clarify all messages related to staging and virtual stages + ). + // Insert it into sequence + insertEvent(stagingEvent). + // Compute burnout time for this stage and add to sAT (this involves activation time and burn time) + SET stageActivationTime TO stageActivationTime + getStageDelays(vehicleIterator:VALUE) + vehicleIterator:VALUE["maxT"]. + // Loop over remaining stages + UNTIL NOT vehicleIterator:NEXT { + // Construct & insert pre-stage event + LOCAL stagingTransitionTime IS stagingKillRotTime. + IF vehicleIterator:VALUE["isVirtualStage"] { SET stagingTransitionTime TO 2. } + LOCAL stagingEvent IS LEXICON( + "time", stageActivationTime - stagingTransitionTime, + "type", "_prestage", + "isVirtual", vehicleIterator:VALUE["isVirtualStage"], + "message", vehicleIterator:VALUE["name"] + ). + insertEvent(stagingEvent). + // Construct & insert staging event + LOCAL stagingEvent IS LEXICON( + "time", stageActivationTime, + "type", "_upfgstage", + "isVirtual", vehicleIterator:VALUE["isVirtualStage"], + "message", vehicleIterator:VALUE["name"] + ). + insertEvent(stagingEvent). + // Compute next stage time + SET stageActivationTime TO stageActivationTime + getStageDelays(vehicleIterator:VALUE) + vehicleIterator:VALUE["maxT"]. + } +} + +// Executes a scheduled sequence/staging event. +FUNCTION eventHandler { + // Clock-based mechanics that is uniform across calls (first call is just like any other) and fully deterministic. + // First we check if we have any events left to execute, and if so - what time should we execute it at. + // Finally, we check whether it's time to handle it, and if so - dispatch to specific handling subroutine. + // Expects global variables: + // "sequence" as list + // "vehicle" as list + // "liftoffTime" as timespan + // "stagingInProgress" as bool + // "steeringRoll" as scalar + // "throttleSetting" as scalar + // "throttleDisplay" as scalar + // "upfgStage" as scalar + // "eventPointer" as scalar + LOCAL nextEventPointer IS eventPointer + 1. + IF nextEventPointer >= sequence:LENGTH { + RETURN. // No more events in the sequence + } + LOCAL nextEventTime IS liftoffTime:SECONDS + sequence[nextEventPointer]["time"]. + IF TIME:SECONDS < nextEventTime { + RETURN. // Not yet time to handle this one. + } + + // If we got this far, means it's time to handle the event + LOCAL event IS sequence[nextEventPointer]. + LOCAL eType IS event["type"]. + IF eType = "print" OR eType = "p" { } + ELSE IF eType = "stage" OR eType = "s" { + STAGE. + } + ELSE IF eType = "jettison" OR eType = "j" { + STAGE. + } + ELSE IF eType = "throttle" OR eType = "t" { + userEvent_throttle(event). + } + ELSE IF eType = "shutdown" OR eType = "u" { + userEvent_shutdown(event). + } + ELSE IF eType = "roll" OR eType = "r" { + userEvent_roll(event). + } + ELSE IF eType = "delegate" OR eType = "d" { + userEvent_delegate(event). + } + ELSE IF eType = "_prestage" { + internalEvent_preStage(). + } + ELSE IF eType = "_upfgstage" { + internalEvent_staging(). + } + ELSE { + pushUIMessage( "Unknown event type (" + eType + ", message='" + event["message"] + "')!", 5, PRIORITY_HIGH ). + } + + // Print event message, if requested + IF event:HASKEY("message") { + pushUIMessage( event["message"] ). + } + + // Mark the event as handled by incrementing the pointer + SET eventPointer TO eventPointer + 1. + + // Run again to handle "overlapping" events + // This is not infinite recursion, because if there is no overlapping event to handle, the next call will RETURN early. + eventHandler(). +} + +// EVENT HANDLING SUBROUTINES + +// Handle the pre-staging event +FUNCTION internalEvent_preStage { + // Switch to staging mode, increment the stage counter and force UPFG reconvergence. + // Rationale is not changed: we want to maintain constant attitude while the current stage is still burning, + // but at the same time start converging guidance for the subsequent stage. + SET stagingInProgress TO TRUE. + SET upfgStage TO upfgStage + 1. + SET upfgConverged TO FALSE. + usc_convergeFlags:CLEAR(). +} + +// Handle the physical staging event +FUNCTION internalEvent_staging { + // Staging event is a potentially complex procedure consisting of several steps. + // Instead of breaking it down into multiple individual events (understood as sequence items), we use the + // trigger mechanism to handle them. When staging occurs, several triggers are scheduled to handle every + // single step of the procedure in a timely manner. + LOCAL currentTime IS TIME:SECONDS. + LOCAL event IS vehicle[upfgStage]["staging"]. + LOCAL stageName IS vehicle[upfgStage]["name"]. + LOCAL eventDelay IS 0. // Keep track of time between subsequent events. + IF upfgStage > 0 AND vehicle[upfgStage-1]["shutdownRequired"] { + SET throttleSetting TO 0. + SET throttleDisplay TO 0. + } + IF event["jettison"] { + GLOBAL stageJettisonTime IS currentTime + event["waitBeforeJettison"]. + WHEN TIME:SECONDS >= stageJettisonTime THEN { + STAGE. + pushUIMessage(stageName + " - separation"). + } + SET eventDelay TO eventDelay + event["waitBeforeJettison"]. + } + IF event["ignition"] { + IF event["ullage"] = "rcs" { + GLOBAL ullageIgnitionTime IS currentTime + eventDelay + event["waitBeforeIgnition"]. + WHEN TIME:SECONDS >= ullageIgnitionTime THEN { + RCS ON. + SET SHIP:CONTROL:FORE TO 1.0. + pushUIMessage(stageName + " - RCS ullage on"). + } + SET eventDelay TO eventDelay + event["waitBeforeIgnition"]. + GLOBAL engineIgnitionTime IS currentTime + eventDelay + event["ullageBurnDuration"]. + WHEN TIME:SECONDS >= engineIgnitionTime THEN { + STAGE. + updateStageEndTime(). + SET stagingInProgress TO FALSE. + pushUIMessage(stageName + " - ignition"). + } + SET eventDelay TO eventDelay + event["ullageBurnDuration"]. + GLOBAL ullageShutdownTime IS currentTime + eventDelay + event["postUllageBurn"]. + WHEN TIME:SECONDS >= ullageShutdownTime THEN { + SET SHIP:CONTROL:FORE TO 0.0. + RCS OFF. + pushUIMessage(stageName + " - RCS ullage off"). + } + } ELSE IF event["ullage"] = "srb" { + GLOBAL ullageIgnitionTime IS currentTime + eventDelay + event["waitBeforeIgnition"]. + WHEN TIME:SECONDS >= ullageIgnitionTime THEN { + STAGE. + pushUIMessage(stageName + " - SRB ullage ignited"). + } + SET eventDelay TO eventDelay + event["waitBeforeIgnition"]. + GLOBAL engineIgnitionTime IS currentTime + eventDelay + event["ullageBurnDuration"]. + WHEN TIME:SECONDS >= engineIgnitionTime THEN { + STAGE. + updateStageEndTime(). + SET stagingInProgress TO FALSE. + pushUIMessage(stageName + " - ignition"). + } + SET eventDelay TO eventDelay + event["ullageBurnDuration"]. + } ELSE IF event["ullage"] = "none" { + GLOBAL engineIgnitionTime IS currentTime + eventDelay + event["waitBeforeIgnition"]. + WHEN TIME:SECONDS >= engineIgnitionTime THEN { + STAGE. + updateStageEndTime(). + SET stagingInProgress TO FALSE. + pushUIMessage(stageName + " - ignition"). + } + SET eventDelay TO eventDelay + event["waitBeforeIgnition"]. + } ELSE { + pushUIMessage( "Unknown event type (" + event["ullage"] + ")!", 5, PRIORITY_HIGH ). + } + } ELSE { + // If this event does not need ignition, staging is over at this moment + SET stagingInProgress TO FALSE. + // If this was the sustainer stage activation event, we also have to: + updateStageEndTime(). + } + + // Print messages for regular stages and constant-acceleration mode activation. + IF NOT vehicle[upfgStage]["isVirtualStage"] { + pushUIMessage(stageName + " - activation"). + } ELSE IF vehicle[upfgStage]["mode"] = 2 { + pushUIMessage("Constant acceleration mode activated."). + } +} + +// Handle the throttle event +FUNCTION userEvent_throttle { + // Throttling is only allowed during the passive guidance phase, as it would ruin burn time predictions used + // for guidance and stage timing. + DECLARE PARAMETER event. // Expects a lexicon + + IF upfgStage < 0 { + IF NOT event:HASKEY("message") { + IF event["throttle"] < throttleSetting { + event:ADD("message", "Throttling down to " + 100*event["throttle"] + "%"). + } ELSE { + event:ADD("message", "Throttling up to " + 100*event["throttle"] + "%"). + } + } + SET throttleSetting TO event["throttle"]. + SET throttleDisplay TO throttleSetting. + } ELSE { + pushUIMessage( "Throttle ignored in active guidance!", 5, PRIORITY_HIGH ). + } +} + +// Handle the engine shutdown event +FUNCTION userEvent_shutdown { + DECLARE PARAMETER event. // Expects a lexicon + + // Find the tagged engines + LOCAL taggedEngines IS SHIP:PARTSTAGGED(event["engineTag"]). + IF taggedEngines:LENGTH = 0 { + pushUIMessage("NO ENGINES TAGGED '" + event["engineTag"] + "' FOUND!", 10, PRIORITY_CRITICAL). + } + // Shut them down + FOR engine IN taggedEngines { + engine:SHUTDOWN(). + } + IF NOT event:HASKEY("message") { + event:ADD("message", "Shutting down engine(s) tagged '" + event["engineTag"] + "'."). + } +} + +// Handle the roll event +FUNCTION userEvent_roll { + DECLARE PARAMETER event. // Expects a lexicon + + SET steeringRoll TO event["angle"]. + IF NOT event:HASKEY("message") { + event:ADD("message", "Rolling to " + steeringRoll + " degrees"). + } +} + +// Handle the delegate event +FUNCTION userEvent_delegate { + DECLARE PARAMETER event. // Expects a lexicon + + LOCAL fun IS event["function"]. + IF fun:ISDEAD() { + pushUIMessage("DEAD DELEGATE - UNABLE TO CALL", 10, PRIORITY_CRITICAL). + } ELSE { + fun:CALL(). + } +} diff --git a/kOS/pegas_util.ks b/kOS/pegas_util.ks index 5113e77..85a5351 100644 --- a/kOS/pegas_util.ks +++ b/kOS/pegas_util.ks @@ -379,7 +379,7 @@ FUNCTION targetNormal { RETURN -vecYZ(normalVec). } -// EVENT HANDLING FUNCTIONS +// VEHICLE DATA PREPARATION FOR UPFG // Setup vehicle: transform user input to UPFG-compatible struct FUNCTION setVehicle { @@ -660,189 +660,17 @@ FUNCTION initializeVehicleForUPFG { SET upfgStage TO 0. // The vehicle is ready so we can start the actual preconvergence for the first active stage } -// Executes a user (sequence) event. -FUNCTION userEventHandler { - // Clock-based mechanics that is uniform across calls (first call is just like any other) and fully in control - // First we check if we have any events left to execute, and if so - what time should we execute it at. - // Finally, we check whether it's time to handle it. +// Utility to keep track of actively guided stage burnouts +FUNCTION updateStageEndTime { + // The staging event calls this when a stage is activated, to calculate when the stage will run out of fuel. + // This is useful, because we can use this value to estimate a true Tgo for that stage. Doubly so, as we can + // easily calculate the total burn time for the entire physical stage. The value might be off by 1-2 seconds + // because of the engine spool-up time (#wontfix). // Expects global variables: - // "sequence" as list // "vehicle" as list - // "liftoffTime" as timespan - // "steeringRoll" as scalar - // "userEventPointer" as scalar // "upfgStage" as scalar - LOCAL nextEventPointer IS userEventPointer + 1. - IF nextEventPointer >= sequence:LENGTH { - RETURN. // No more events in the sequence - } - LOCAL nextEventTime IS liftoffTime:SECONDS + sequence[nextEventPointer]["time"]. - IF TIME:SECONDS < nextEventTime { - RETURN. // Not yet time to handle this one. - } - - // If we got this far, means it's time to handle the event - LOCAL event IS sequence[nextEventPointer]. - LOCAL eType IS event["type"]. - IF eType = "print" OR eType = "p" { } - ELSE IF eType = "stage" OR eType = "s" { STAGE. } - ELSE IF eType = "jettison" OR eType = "j" { - // We used to handle the reduction of vehicle mass here, but it has since been moved to initializeVehicleForUPFG a few lines up. - STAGE. - } - ELSE IF eType = "throttle" OR eType = "t" { - // Throttling is only allowed during the passive guidance phase, as it would ruin burn time predictions used - // for guidance and stage timing. - IF upfgStage < 0 { - IF NOT event:HASKEY("message") { - IF event["throttle"] < throttleSetting { - event:ADD("message", "Throttling down to " + 100*event["throttle"] + "%"). - } ELSE { - event:ADD("message", "Throttling up to " + 100*event["throttle"] + "%"). - } - } - SET throttleSetting TO event["throttle"]. - SET throttleDisplay TO throttleSetting. - } ELSE { - pushUIMessage( "Throttle ignored in active guidance!", 5, PRIORITY_HIGH ). - } - } - ELSE IF eType = "shutdown" OR eType = "u" { - // Find the tagged engines - LOCAL taggedEngines IS SHIP:PARTSTAGGED(event["engineTag"]). - IF taggedEngines:LENGTH = 0 { - pushUIMessage("NO ENGINES TAGGED '" + event["engineTag"] + "' FOUND!", 10, PRIORITY_CRITICAL). - } - // Shut them down - FOR engine IN taggedEngines { - engine:SHUTDOWN(). - } - IF NOT event:HASKEY("message") { - event:ADD("message", "Shutting down engine(s) tagged '" + event["engineTag"] + "'."). - } - } - ELSE IF eType = "roll" OR eType = "r" { - SET steeringRoll TO event["angle"]. - IF NOT event:HASKEY("message") { - event:ADD("message", "Rolling to " + steeringRoll + " degrees"). - } - } - ELSE IF eType = "delegate" OR eType = "d" { - event["function"]:CALL(). - } - ELSE IF eType = "_prestage" { - internalEvent_preStage(). - } - ELSE IF eType = "_upfgstage" { - internalEvent_staging(). - } - ELSE { pushUIMessage( "Unknown event type (" + eType + ", message='" + event["message"] + "')!", 5, PRIORITY_HIGH ). } - - // Print event message, if requested - IF event:HASKEY("message") { - pushUIMessage( event["message"] ). - } - - // Mark the event as handled by incrementing the pointer - SET userEventPointer TO userEventPointer + 1. - - // Run again to handle "overlapping" events - // This is not infinite recursion, because if there is no overlapping event to handle, the next call will RETURN early. - userEventHandler(). -} - -FUNCTION internalEvent_preStage { - SET stagingInProgress TO TRUE. - SET upfgStage TO upfgStage + 1. - SET upfgConverged TO FALSE. - usc_convergeFlags:CLEAR(). -} - -FUNCTION internalEvent_staging { - LOCAL currentTime IS TIME:SECONDS. - LOCAL event IS vehicle[upfgStage]["staging"]. - LOCAL stageName IS vehicle[upfgStage]["name"]. - LOCAL eventDelay IS 0. // Keep track of time between subsequent events. - IF upfgStage > 0 AND vehicle[upfgStage-1]["shutdownRequired"] { - SET throttleSetting TO 0. - SET throttleDisplay TO 0. - } - IF event["jettison"] { - GLOBAL stageJettisonTime IS currentTime + event["waitBeforeJettison"]. - WHEN TIME:SECONDS >= stageJettisonTime THEN { - STAGE. - pushUIMessage(stageName + " - separation"). - } - SET eventDelay TO eventDelay + event["waitBeforeJettison"]. - } - IF event["ignition"] { - IF event["ullage"] = "rcs" { - GLOBAL ullageIgnitionTime IS currentTime + eventDelay + event["waitBeforeIgnition"]. - WHEN TIME:SECONDS >= ullageIgnitionTime THEN { - RCS ON. - SET SHIP:CONTROL:FORE TO 1.0. - pushUIMessage(stageName + " - RCS ullage on"). - } - SET eventDelay TO eventDelay + event["waitBeforeIgnition"]. - GLOBAL engineIgnitionTime IS currentTime + eventDelay + event["ullageBurnDuration"]. - WHEN TIME:SECONDS >= engineIgnitionTime THEN { - STAGE. - updateStageEndTime(). - SET stagingInProgress TO FALSE. - pushUIMessage(stageName + " - ignition"). - } - SET eventDelay TO eventDelay + event["ullageBurnDuration"]. - GLOBAL ullageShutdownTime IS currentTime + eventDelay + event["postUllageBurn"]. - WHEN TIME:SECONDS >= ullageShutdownTime THEN { - SET SHIP:CONTROL:FORE TO 0.0. - RCS OFF. - pushUIMessage(stageName + " - RCS ullage off"). - } - } ELSE IF event["ullage"] = "srb" { - GLOBAL ullageIgnitionTime IS currentTime + eventDelay + event["waitBeforeIgnition"]. - WHEN TIME:SECONDS >= ullageIgnitionTime THEN { - STAGE. - pushUIMessage(stageName + " - SRB ullage ignited"). - } - SET eventDelay TO eventDelay + event["waitBeforeIgnition"]. - GLOBAL engineIgnitionTime IS currentTime + eventDelay + event["ullageBurnDuration"]. - WHEN TIME:SECONDS >= engineIgnitionTime THEN { - STAGE. - updateStageEndTime(). - SET stagingInProgress TO FALSE. - pushUIMessage(stageName + " - ignition"). - } - SET eventDelay TO eventDelay + event["ullageBurnDuration"]. - } ELSE IF event["ullage"] = "none" { - GLOBAL engineIgnitionTime IS currentTime + eventDelay + event["waitBeforeIgnition"]. - WHEN TIME:SECONDS >= engineIgnitionTime THEN { - STAGE. - updateStageEndTime(). - SET stagingInProgress TO FALSE. - pushUIMessage(stageName + " - ignition"). - } - SET eventDelay TO eventDelay + event["waitBeforeIgnition"]. - } ELSE { - pushUIMessage( "Unknown event type (" + event["ullage"] + ")!", 5, PRIORITY_HIGH ). - } - } ELSE { - // If this event does not need ignition, staging is over at this moment - SET stagingInProgress TO FALSE. - // If this was the sustainer stage activation event, we also have to: - updateStageEndTime(). - } - - // Print messages for regular stages and constant-acceleration mode activation. - IF NOT vehicle[upfgStage]["isVirtualStage"] { - pushUIMessage(stageName + " - activation"). - } ELSE IF vehicle[upfgStage]["mode"] = 2 { - pushUIMessage("Constant acceleration mode activated."). - } -} - -FUNCTION updateStageEndTime { LOCAL stageBurnTime IS 0. - // Calculate the complete burn time of the entire physical stage, including the subsequent virtuals + // Calculate the complete burn time including the subsequent virtual stages. LOCAL i IS 0. FOR stg in vehicle { // Forward to the current stage @@ -891,11 +719,20 @@ FUNCTION upfgSteeringControl { pushUIMessage( "UPFG reset", 5, PRIORITY_CRITICAL ). } - // Expects global variables "upfgConverged" and "stagingInProgress" as bool, "steeringVector" as vector, - // "upfgConvergenceCriterion", "upfgGoodSolutionCriterion", "steeringRoll" and "liftoffTime" as scalars, - // "vehicle" as list, and "controls" as lexicon. - // Owns global variables "usc_lastGoodVector" as vector, "usc_convergeFlags" as list, and - // "usc_lastIterationTime" as scalar. + // Expects global variables: + // "upfgConverged" as bool + // "stagingInProgress" as bool + // "steeringVector" as vector + // "upfgConvergenceCriterion" as scalar + // "upfgGoodSolutionCriterion" as scalar + // "steeringRoll" as scalar + // "liftoffTime" as timespan + // "vehicle" as list + // "controls" as lexicon + // Owns global variables: + // "usc_lastGoodVector" as vector + // "usc_convergeFlags" as list + // "usc_lastIterationTime" as scalar DECLARE PARAMETER vehicle. // Expects a list of lexicon DECLARE PARAMETER upfgStage. // Expects a scalar DECLARE PARAMETER upfgTarget. // Expects a lexicon @@ -994,65 +831,3 @@ FUNCTION throttleControl { } ELSE { pushUIMessage( "throttleControl stage error (stage=" + upfgStage + "(" + whichStage + "), mode=" + vehicle[whichStage]["mode"] + ")!", 5, PRIORITY_CRITICAL ). }. } - -// Create sequence entries for staging events -FUNCTION spawnStagingEvents { - // For each active stage we have to schedule the preStage event and the staging event - except the FIRST ONE which is already - // preStaged by now and we just need to stage it (which will possibly be a no-op if it's a sustainer). We'll iterate over the - // vehicle, computing (cumulatively) burnout times for each stage and spawning events. We know exactly when everything starts: - // `controls["upfgActivation"]`. - LOCAL stageActivationTime IS controls["upfgActivation"]. - LOCAL vehicleIterator IS vehicle:ITERATOR. - // The first active stage is already pre-staged so we only need to create the staging event - vehicleIterator:NEXT. - LOCAL stagingEvent IS LEXICON( - "time", stageActivationTime, - "type", "_upfgstage", - "isVirtual", vehicleIterator:VALUE["isVirtualStage"], - "message", "active guidance on" // todo: clarify all messages related to staging and virtual stages - ). - // Insert it into sequence - insertEvent(stagingEvent). - // Compute burnout time for this stage and add to sAT (this involves activation time and burn time) - SET stageActivationTime TO stageActivationTime + getStageDelays(vehicleIterator:VALUE) + vehicleIterator:VALUE["maxT"]. - // Loop over remaining stages - UNTIL NOT vehicleIterator:NEXT { - // Construct & insert pre-stage event - LOCAL stagingTransitionTime IS stagingKillRotTime. - IF vehicleIterator:VALUE["isVirtualStage"] { SET stagingTransitionTime TO 2. } - LOCAL stagingEvent IS LEXICON( - "time", stageActivationTime - stagingTransitionTime, - "type", "_prestage", - "isVirtual", vehicleIterator:VALUE["isVirtualStage"], - "message", vehicleIterator:VALUE["name"] - ). - insertEvent(stagingEvent). - // Construct & insert staging event - LOCAL stagingEvent IS LEXICON( - "time", stageActivationTime, - "type", "_upfgstage", - "isVirtual", vehicleIterator:VALUE["isVirtualStage"], - "message", vehicleIterator:VALUE["name"] - ). - insertEvent(stagingEvent). - // Compute next stage time - SET stageActivationTime TO stageActivationTime + getStageDelays(vehicleIterator:VALUE) + vehicleIterator:VALUE["maxT"]. - } -} - -// Insert an event into the sequence by time order -FUNCTION insertEvent { - DECLARE PARAMETER event. - LOCAL eventTime IS event["time"]. - // Go over the list to find the index of insertion... - LOCAL index IS 0. - FOR this IN sequence { - IF eventTime < this["time"] { - // This will cause insertion of the new item AFTER any other item with exact same timing - BREAK. - } - SET index TO index + 1. - } - // ...and insert there. - sequence:INSERT(index, event). -}