mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Improve the consistency of the module intros to the most commonly used version (single dash). Add missing module information (abbreviated where none existed previously). Fix broken documentation links Make module names correspond to filenames (and fix links). Fix typos.
378 lines
16 KiB
Lua
378 lines
16 KiB
Lua
--- **Core** - SCHEDULEDISPATCHER dispatches the different schedules.
|
|
--
|
|
-- ===
|
|
--
|
|
-- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects.
|
|
--
|
|
-- This class is tricky and needs some thorough explanation.
|
|
-- SCHEDULE classes are used to schedule functions for objects, or as persistent objects.
|
|
-- The SCHEDULEDISPATCHER class ensures that:
|
|
--
|
|
-- - Scheduled functions are planned according the SCHEDULER object parameters.
|
|
-- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters.
|
|
-- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters.
|
|
--
|
|
-- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection:
|
|
--
|
|
-- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER object is _persistent_ within memory.
|
|
-- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection!
|
|
--
|
|
-- The non-persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collected when the parent object is destroyed, or set to nil and garbage collected.
|
|
-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object,
|
|
-- these will not be executed anymore when the SCHEDULER object has been destroyed.
|
|
--
|
|
-- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object.
|
|
-- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER.
|
|
-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method.
|
|
-- The Schedule() method returns the CallID that is the reference ID for each planned schedule.
|
|
--
|
|
-- ===
|
|
--
|
|
-- ### Contributions: -
|
|
-- ### Authors: FlightControl : Design & Programming
|
|
--
|
|
-- @module Core.ScheduleDispatcher
|
|
-- @image Core_Schedule_Dispatcher.JPG
|
|
|
|
--- SCHEDULEDISPATCHER class.
|
|
-- @type SCHEDULEDISPATCHER
|
|
-- @field #string ClassName Name of the class.
|
|
-- @field #number CallID Call ID counter.
|
|
-- @field #table PersistentSchedulers Persistent schedulers.
|
|
-- @field #table ObjectSchedulers Schedulers that only exist as long as the master object exists.
|
|
-- @field #table Schedule Meta table setmetatable( {}, { __mode = "k" } ).
|
|
-- @extends Core.Base#BASE
|
|
|
|
--- The SCHEDULEDISPATCHER structure
|
|
-- @type SCHEDULEDISPATCHER
|
|
SCHEDULEDISPATCHER = {
|
|
ClassName = "SCHEDULEDISPATCHER",
|
|
CallID = 0,
|
|
PersistentSchedulers = {},
|
|
ObjectSchedulers = {},
|
|
Schedule = nil,
|
|
}
|
|
|
|
--- Player data table holding all important parameters of each player.
|
|
-- @type SCHEDULEDISPATCHER.ScheduleData
|
|
-- @field #function Function The schedule function to be called.
|
|
-- @field #table Arguments Schedule function arguments.
|
|
-- @field #number Start Start time in seconds.
|
|
-- @field #number Repeat Repeat time interval in seconds.
|
|
-- @field #number Randomize Randomization factor [0,1].
|
|
-- @field #number Stop Stop time in seconds.
|
|
-- @field #number StartTime Time in seconds when the scheduler is created.
|
|
-- @field #number ScheduleID Schedule ID.
|
|
-- @field #function CallHandler Function to be passed to the DCS timer.scheduleFunction().
|
|
-- @field #boolean ShowTrace If true, show tracing info.
|
|
|
|
--- Create a new schedule dispatcher object.
|
|
-- @param #SCHEDULEDISPATCHER self
|
|
-- @return #SCHEDULEDISPATCHER self
|
|
function SCHEDULEDISPATCHER:New()
|
|
local self = BASE:Inherit( self, BASE:New() )
|
|
self:F3()
|
|
return self
|
|
end
|
|
|
|
--- Add a Schedule to the ScheduleDispatcher.
|
|
-- The development of this method was really tidy.
|
|
-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is set to nil.
|
|
-- Nothing of this code should be modified without testing it thoroughly.
|
|
-- @param #SCHEDULEDISPATCHER self
|
|
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
|
-- @param #function ScheduleFunction Scheduler function.
|
|
-- @param #table ScheduleArguments Table of arguments passed to the ScheduleFunction.
|
|
-- @param #number Start Start time in seconds.
|
|
-- @param #number Repeat Repeat interval in seconds.
|
|
-- @param #number Randomize Randomization factor [0,1].
|
|
-- @param #number Stop Stop time in seconds.
|
|
-- @param #number TraceLevel Trace level [0,3].
|
|
-- @param Core.Fsm#FSM Fsm Finite state model.
|
|
-- @return #string Call ID or nil.
|
|
function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop, TraceLevel, Fsm )
|
|
self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop, TraceLevel, Fsm } )
|
|
|
|
-- Increase counter.
|
|
self.CallID = self.CallID + 1
|
|
|
|
-- Create ID.
|
|
local CallID = self.CallID .. "#" .. (Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "") or ""
|
|
|
|
self:T2( string.format( "Adding schedule #%d CallID=%s", self.CallID, CallID ) )
|
|
|
|
-- Initialize PersistentSchedulers
|
|
self.PersistentSchedulers = self.PersistentSchedulers or {}
|
|
|
|
-- Initialize the ObjectSchedulers array, which is a weakly coupled table.
|
|
-- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array.
|
|
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
|
|
|
|
if Scheduler.MasterObject then
|
|
--env.info("FF Object Scheduler")
|
|
self.ObjectSchedulers[CallID] = Scheduler
|
|
self:F3( { CallID = CallID, ObjectScheduler = tostring( self.ObjectSchedulers[CallID] ), MasterObject = tostring( Scheduler.MasterObject ) } )
|
|
else
|
|
--env.info("FF Persistent Scheduler")
|
|
self.PersistentSchedulers[CallID] = Scheduler
|
|
self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } )
|
|
end
|
|
|
|
self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } )
|
|
self.Schedule[Scheduler] = self.Schedule[Scheduler] or {}
|
|
self.Schedule[Scheduler][CallID] = {} -- #SCHEDULEDISPATCHER.ScheduleData
|
|
self.Schedule[Scheduler][CallID].Function = ScheduleFunction
|
|
self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments
|
|
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 )
|
|
self.Schedule[Scheduler][CallID].Start = Start + 0.001
|
|
self.Schedule[Scheduler][CallID].Repeat = Repeat or 0
|
|
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
|
|
self.Schedule[Scheduler][CallID].Stop = Stop
|
|
|
|
-- This section handles the tracing of the scheduled calls.
|
|
-- Because these calls will be executed with a delay, we inspect the place where these scheduled calls are initiated.
|
|
-- The Info structure contains the output of the debug.getinfo() calls, which inspects the call stack for the function name, line number and source name.
|
|
-- The call stack has many levels, and the correct semantical function call depends on where in the code AddSchedule was "used".
|
|
-- - Using SCHEDULER:New()
|
|
-- - Using Schedule:AddSchedule()
|
|
-- - Using Fsm:__Func()
|
|
-- - Using Class:ScheduleOnce()
|
|
-- - Using Class:ScheduleRepeat()
|
|
-- - ...
|
|
-- So for each of these scheduled call variations, AddSchedule is the workhorse which will schedule the call.
|
|
-- But the correct level with the correct semantical function location will differ depending on the above scheduled call invocation forms.
|
|
-- That's where the field TraceLevel contains optionally the level in the call stack where the call information is obtained.
|
|
-- The TraceLevel field indicates the correct level where the semantical scheduled call was invoked within the source, ensuring that function name, line number and source name are correct.
|
|
-- There is one quick ...
|
|
-- The FSM class models scheduled calls using the __Func syntax. However, these functions are "tailed".
|
|
-- There aren't defined anywhere within the source code, but rather implemented as triggers within the FSM logic,
|
|
-- and using the onbefore, onafter, onenter, onleave prefixes. (See the FSM for details).
|
|
-- Therefore, in the call stack, at the TraceLevel these functions are mentioned as "tail calls", and the Info.name field will be nil as a result.
|
|
-- To obtain the correct function name for FSM object calls, the function is mentioned in the call stack at a higher stack level.
|
|
-- So when function name stored in Info.name is nil, then I inspect the function name within the call stack one level higher.
|
|
-- So this little piece of code does its magic wonderfully, performance overhead is negligible, as scheduled calls don't happen that often.
|
|
|
|
local Info = {}
|
|
|
|
if debug then
|
|
TraceLevel = TraceLevel or 2
|
|
Info = debug.getinfo( TraceLevel, "nlS" )
|
|
local name_fsm = debug.getinfo( TraceLevel - 1, "n" ).name -- #string
|
|
if name_fsm then
|
|
Info.name = name_fsm
|
|
end
|
|
end
|
|
|
|
self:T3( self.Schedule[Scheduler][CallID] )
|
|
|
|
--- Function passed to the DCS timer.scheduleFunction()
|
|
self.Schedule[Scheduler][CallID].CallHandler = function( Params )
|
|
|
|
local CallID = Params.CallID
|
|
local Info = Params.Info or {}
|
|
local Source = Info.source or "?"
|
|
local Line = Info.currentline or "?"
|
|
local Name = Info.name or "?"
|
|
|
|
local ErrorHandler = function( errmsg )
|
|
env.info( "Error in timer function: " .. errmsg )
|
|
if BASE.Debug ~= nil then
|
|
env.info( BASE.Debug.traceback() )
|
|
end
|
|
return errmsg
|
|
end
|
|
|
|
-- Get object or persistent scheduler object.
|
|
local Scheduler = self.ObjectSchedulers[CallID] -- Core.Scheduler#SCHEDULER
|
|
if not Scheduler then
|
|
Scheduler = self.PersistentSchedulers[CallID]
|
|
end
|
|
|
|
-- self:T3( { Scheduler = Scheduler } )
|
|
|
|
if Scheduler then
|
|
|
|
local MasterObject = tostring( Scheduler.MasterObject )
|
|
|
|
-- Schedule object.
|
|
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
|
|
|
-- self:T3( { Schedule = Schedule } )
|
|
|
|
local SchedulerObject = Scheduler.MasterObject -- Scheduler.SchedulerObject Now is this the Master or Scheduler object?
|
|
local ShowTrace = Scheduler.ShowTrace
|
|
|
|
local ScheduleFunction = Schedule.Function
|
|
local ScheduleArguments = Schedule.Arguments or {}
|
|
local Start = Schedule.Start
|
|
local Repeat = Schedule.Repeat or 0
|
|
local Randomize = Schedule.Randomize or 0
|
|
local Stop = Schedule.Stop or 0
|
|
local ScheduleID = Schedule.ScheduleID
|
|
|
|
local Prefix = (Repeat == 0) and "--->" or "+++>"
|
|
|
|
local Status, Result
|
|
-- self:E( { SchedulerObject = SchedulerObject } )
|
|
if SchedulerObject then
|
|
local function Timer()
|
|
if ShowTrace then
|
|
SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
|
end
|
|
-- The master object is passed as first parameter. A few :Schedule() calls in MOOSE expect this currently. But in principle it should be removed.
|
|
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
|
|
end
|
|
Status, Result = xpcall( Timer, ErrorHandler )
|
|
else
|
|
local function Timer()
|
|
if ShowTrace then
|
|
self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
|
end
|
|
return ScheduleFunction( unpack( ScheduleArguments ) )
|
|
end
|
|
Status, Result = xpcall( Timer, ErrorHandler )
|
|
end
|
|
|
|
local CurrentTime = timer.getTime()
|
|
local StartTime = Schedule.StartTime
|
|
|
|
-- Debug info.
|
|
self:F3( { CallID = CallID, ScheduleID = ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
|
|
|
|
if Status and ((Result == nil) or (Result and Result ~= false)) then
|
|
|
|
if Repeat ~= 0 and ((Stop == 0) or (Stop ~= 0 and CurrentTime <= StartTime + Stop)) then
|
|
local ScheduleTime = CurrentTime + Repeat + math.random( -(Randomize * Repeat / 2), (Randomize * Repeat / 2) ) + 0.0001 -- Accuracy
|
|
-- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
|
|
return ScheduleTime -- returns the next time the function needs to be called.
|
|
else
|
|
self:Stop( Scheduler, CallID )
|
|
end
|
|
|
|
else
|
|
self:Stop( Scheduler, CallID )
|
|
end
|
|
else
|
|
self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
self:Start( Scheduler, CallID, Info )
|
|
|
|
return CallID
|
|
end
|
|
|
|
--- Remove schedule.
|
|
-- @param #SCHEDULEDISPATCHER self
|
|
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
|
-- @param #table CallID Call ID.
|
|
function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID )
|
|
self:F2( { Remove = CallID, Scheduler = Scheduler } )
|
|
|
|
if CallID then
|
|
self:Stop( Scheduler, CallID )
|
|
self.Schedule[Scheduler][CallID] = nil
|
|
end
|
|
end
|
|
|
|
--- Start dispatcher.
|
|
-- @param #SCHEDULEDISPATCHER self
|
|
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
|
-- @param #table CallID (Optional) Call ID.
|
|
-- @param #string Info (Optional) Debug info.
|
|
function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info )
|
|
self:F2( { Start = CallID, Scheduler = Scheduler } )
|
|
|
|
if CallID then
|
|
|
|
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
|
|
|
-- Only start when there is no ScheduleID defined!
|
|
-- This prevents to "Start" the scheduler twice with the same CallID...
|
|
if not Schedule.ScheduleID then
|
|
|
|
-- Current time in seconds.
|
|
local Tnow = timer.getTime()
|
|
|
|
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
|
|
|
|
-- Start DCS schedule function https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction
|
|
Schedule.ScheduleID = timer.scheduleFunction( Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start )
|
|
|
|
self:T( string.format( "Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
|
|
end
|
|
|
|
else
|
|
|
|
-- Recursive.
|
|
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
|
|
self:Start( Scheduler, CallID, Info ) -- Recursive
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
--- Stop dispatcher.
|
|
-- @param #SCHEDULEDISPATCHER self
|
|
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
|
-- @param #string CallID (Optional) Scheduler Call ID. If nil, all pending schedules are stopped recursively.
|
|
function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
|
|
self:F2( { Stop = CallID, Scheduler = Scheduler } )
|
|
|
|
if CallID then
|
|
|
|
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
|
|
|
-- Only stop when there is a ScheduleID defined for the CallID. So, when the scheduler was stopped before, do nothing.
|
|
if Schedule.ScheduleID then
|
|
|
|
self:T( string.format( "SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
|
|
|
|
-- Remove schedule function https://wiki.hoggitworld.com/view/DCS_func_removeFunction
|
|
timer.removeFunction( Schedule.ScheduleID )
|
|
|
|
Schedule.ScheduleID = nil
|
|
|
|
else
|
|
self:T( string.format( "Error no ScheduleID for CallID=%s", tostring( CallID ) ) )
|
|
end
|
|
|
|
else
|
|
|
|
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
|
|
self:Stop( Scheduler, CallID ) -- Recursive
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
--- Clear all schedules by stopping all dispatchers.
|
|
-- @param #SCHEDULEDISPATCHER self
|
|
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
|
function SCHEDULEDISPATCHER:Clear( Scheduler )
|
|
self:F2( { Scheduler = Scheduler } )
|
|
|
|
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
|
|
self:Stop( Scheduler, CallID ) -- Recursive
|
|
end
|
|
end
|
|
|
|
--- Show tracing info.
|
|
-- @param #SCHEDULEDISPATCHER self
|
|
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
|
function SCHEDULEDISPATCHER:ShowTrace( Scheduler )
|
|
self:F2( { Scheduler = Scheduler } )
|
|
Scheduler.ShowTrace = true
|
|
end
|
|
|
|
--- No tracing info.
|
|
-- @param #SCHEDULEDISPATCHER self
|
|
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
|
function SCHEDULEDISPATCHER:NoTrace( Scheduler )
|
|
self:F2( { Scheduler = Scheduler } )
|
|
Scheduler.ShowTrace = false
|
|
end
|
|
|