First prototype of the scheduler dispatcher is ready... It works, but the code was very difficult...

So, when the Scheduler that is passed to the AddScheduler is nillified,
the internal arrays that keep the Scheduler reference are also
nillified. And it does what i need for further utilization in MOOSE
classes. When the Scheduler is nillified, but, a schedule was planned
for that Scheduler, once the scheduler fires off, it will ignore that
call... cool.

Sven
This commit is contained in:
FlightControl 2016-12-12 16:44:56 +01:00
parent 24a6d37500
commit b81b483f0b
17 changed files with 481 additions and 174 deletions

View File

@ -1,6 +1,19 @@
--- The EVENT class models an efficient event handling process between other classes and its units, weapons.
--- This module contains the EVENT class.
--
-- ===
--
-- Takes care of EVENT dispatching between DCS events and event handling functions defined in MOOSE classes.
--
-- ===
--
-- The above menus classes **are derived** from 2 main **abstract** classes defined within the MOOSE framework (so don't use these):
--
-- ===
--
-- ### Contributions: -
-- ### Authors: FlightControl : Design & Programming
--
-- @module Event
-- @author FlightControl
--- The EVENT structure
-- @type EVENT
@ -93,7 +106,7 @@ function EVENT:Init( EventID, EventClass )
end
if not self.Events[EventID][EventClass] then
self.Events[EventID][EventClass] = setmetatable( {}, { __mode = "v" } )
self.Events[EventID][EventClass] = setmetatable( {}, { __mode = "k" } )
end
return self.Events[EventID][EventClass]
end

View File

@ -58,26 +58,6 @@ function SCHEDULER:New( TimeEventObject, TimeEventFunction, TimeEventFunctionArg
local self = BASE:Inherit( self, BASE:New() )
self:F2( { StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } )
self:Schedule( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds )
return self
end
--- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also.
-- @param #SCHEDULER self
-- @param #table TimeEventObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference.
-- @param #function TimeEventFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in TimeEventFunctionArguments.
-- @param #table TimeEventFunctionArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @param #number StartSeconds Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
-- @param #number RepeatSecondsInterval Specifies the interval in seconds when the scheduler will call the event function.
-- @param #number RandomizationFactor Specifies a randomization factor between 0 and 1 to randomize the RepeatSecondsInterval.
-- @param #number StopSeconds Specifies the amount of seconds when the scheduler will be stopped.
-- @return #SCHEDULER self
function SCHEDULER:Schedule( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds )
self:F2( { StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } )
self:T3( { TimeEventFunctionArguments } )
self.TimeEventObject = TimeEventObject
self.TimeEventFunction = TimeEventFunction
self.TimeEventFunctionArguments = TimeEventFunctionArguments
@ -89,103 +69,11 @@ function SCHEDULER:Schedule( TimeEventObject, TimeEventFunction, TimeEventFuncti
self.StartTime = timer.getTime()
self:Start()
_TIMERDISPATCHER:AddSchedule( self )
return self
end
--- (Re-)Starts the scheduler.
-- @param #SCHEDULER self
-- @return #SCHEDULER self
function SCHEDULER:Start()
self:F2()
if self.RepeatSecondsInterval ~= 0 then
self.Repeat = true
end
if self.StartSeconds then
if self.ScheduleID then
timer.removeFunction( self.ScheduleID )
end
self:T( { self.StartSeconds } )
self.ScheduleID = timer.scheduleFunction( self._Scheduler, self, timer.getTime() + self.StartSeconds + .001 )
end
return self
end
--- Stops the scheduler.
-- @param #SCHEDULER self
-- @return #SCHEDULER self
function SCHEDULER:Stop()
self:F2( self.TimeEventObject )
self.Repeat = false
if self.ScheduleID then
self:E( "Stop Schedule" )
timer.removeFunction( self.ScheduleID )
end
self.ScheduleID = nil
return self
end
-- Private Functions
--- @param #SCHEDULER self
function SCHEDULER:_Scheduler()
self:F2( self.TimeEventFunctionArguments )
local ErrorHandler = function( errmsg )
env.info( "Error in SCHEDULER function:" .. errmsg )
if debug ~= nil then
env.info( debug.traceback() )
end
return errmsg
end
local StartTime = self.StartTime
local StopSeconds = self.StopSeconds
local Repeat = self.Repeat
local RandomizationFactor = self.RandomizationFactor
local RepeatSecondsInterval = self.RepeatSecondsInterval
local ScheduleID = self.ScheduleID
local Status, Result
if self.TimeEventObject then
Status, Result = xpcall( function() return self.TimeEventFunction( self.TimeEventObject, unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler )
else
Status, Result = xpcall( function() return self.TimeEventFunction( unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler )
end
self:T( { "Timer Event2 .. " .. self.ScheduleID, Status, Result, StartTime, RepeatSecondsInterval, RandomizationFactor, StopSeconds } )
if Status and ( ( Result == nil ) or ( Result and Result ~= false ) ) then
if Repeat and ( not StopSeconds or ( StopSeconds and timer.getTime() <= StartTime + StopSeconds ) ) then
local ScheduleTime =
timer.getTime() +
self.RepeatSecondsInterval +
math.random(
- ( RandomizationFactor * RepeatSecondsInterval / 2 ),
( RandomizationFactor * RepeatSecondsInterval / 2 )
) +
0.01
self:T( { self.TimeEventFunctionArguments, "Repeat:", timer.getTime(), ScheduleTime } )
return ScheduleTime -- returns the next time the function needs to be called.
else
timer.removeFunction( ScheduleID )
self.ScheduleID = nil
end
else
timer.removeFunction( ScheduleID )
self.ScheduleID = nil
end
return nil
end

View File

@ -0,0 +1,214 @@
--- This module contains the SCHEDULER class.
--
-- 1) @{Core.Scheduler#SCHEDULER} class, extends @{Core.Base#BASE}
-- =====================================================
-- The @{Core.Scheduler#SCHEDULER} class models time events calling given event handling functions.
--
-- 1.1) SCHEDULER constructor
-- --------------------------
-- The SCHEDULER class is quite easy to use:
--
-- * @{Core.Scheduler#SCHEDULER.New}: Setup a new scheduler and start it with the specified parameters.
--
-- 1.2) SCHEDULER timer stop and start
-- -----------------------------------
-- The SCHEDULER can be stopped and restarted with the following methods:
--
-- * @{Core.Scheduler#SCHEDULER.Start}: (Re-)Start the scheduler.
-- * @{Core.Scheduler#SCHEDULER.Stop}: Stop the scheduler.
--
-- 1.3) Reschedule new time event
-- ------------------------------
-- With @{Core.Scheduler#SCHEDULER.Schedule} a new time event can be scheduled.
--
-- ===
--
-- ### Contributions:
--
-- * Mechanist : Concept & Testing
--
-- ### Authors:
--
-- * FlightControl : Design & Programming
--
-- ===
--
-- @module Scheduler
--- The SCHEDULER class
-- @type SCHEDULER
-- @field #number ScheduleID the ID of the scheduler.
-- @extends Core.Base#BASE
SCHEDULER = {
ClassName = "SCHEDULER",
}
--- SCHEDULER constructor.
-- @param #SCHEDULER self
-- @param #table TimeEventObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference.
-- @param #function TimeEventFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in TimeEventFunctionArguments.
-- @param #table TimeEventFunctionArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @param #number StartSeconds Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
-- @param #number RepeatSecondsInterval Specifies the interval in seconds when the scheduler will call the event function.
-- @param #number RandomizationFactor Specifies a randomization factor between 0 and 1 to randomize the RepeatSecondsInterval.
-- @param #number StopSeconds Specifies the amount of seconds when the scheduler will be stopped.
-- @return #SCHEDULER self
function SCHEDULER:New( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds )
local self = BASE:Inherit( self, BASE:New() )
self:F2( { StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } )
self.TimeEventObject = TimeEventObject
self.TimeEventFunction = TimeEventFunction
self.TimeEventFunctionArguments = TimeEventFunctionArguments
self.StartSeconds = StartSeconds
self.Repeat = false
self.RepeatSecondsInterval = RepeatSecondsInterval or 0
self.RandomizationFactor = RandomizationFactor or 0
self.StopSeconds = StopSeconds
self.StartTime = timer.getTime()
_TIMERDISPATCHER:AddSchedule( self )
return self
end
--- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also.
-- @param #SCHEDULER self
-- @param #table TimeEventObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference.
-- @param #function TimeEventFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in TimeEventFunctionArguments.
-- @param #table TimeEventFunctionArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @param #number StartSeconds Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
-- @param #number RepeatSecondsInterval Specifies the interval in seconds when the scheduler will call the event function.
-- @param #number RandomizationFactor Specifies a randomization factor between 0 and 1 to randomize the RepeatSecondsInterval.
-- @param #number StopSeconds Specifies the amount of seconds when the scheduler will be stopped.
-- @return #SCHEDULER self
function SCHEDULER:Schedule( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds )
self:F2( { StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } )
self:T3( { TimeEventFunctionArguments } )
self.TimeEventObject = TimeEventObject
self.TimeEventFunction = TimeEventFunction
self.TimeEventFunctionArguments = TimeEventFunctionArguments
self.StartSeconds = StartSeconds
self.Repeat = false
self.RepeatSecondsInterval = RepeatSecondsInterval or 0
self.RandomizationFactor = RandomizationFactor or 0
self.StopSeconds = StopSeconds
self.StartTime = timer.getTime()
self:Start()
return self
end
--- (Re-)Starts the scheduler.
-- @param #SCHEDULER self
-- @return #SCHEDULER self
function SCHEDULER:Start()
self:F2()
if self.RepeatSecondsInterval ~= 0 then
self.Repeat = true
end
if self.StartSeconds then
if self.ScheduleID then
timer.removeFunction( self.ScheduleID )
end
self:T( { self.StartSeconds } )
self.ScheduleID = timer.scheduleFunction( self._Scheduler, self, timer.getTime() + self.StartSeconds + .001 )
end
return self
end
--- Stops the scheduler.
-- @param #SCHEDULER self
-- @return #SCHEDULER self
function SCHEDULER:Stop()
self:F2( self.TimeEventObject )
self.Repeat = false
if self.ScheduleID then
self:E( "Stop Schedule" )
timer.removeFunction( self.ScheduleID )
end
self.ScheduleID = nil
return self
end
-- Private Functions
--- @param #SCHEDULER self
function SCHEDULER:_Scheduler()
self:F2( self.TimeEventFunctionArguments )
local ErrorHandler = function( errmsg )
env.info( "Error in SCHEDULER function:" .. errmsg )
if debug ~= nil then
env.info( debug.traceback() )
end
return errmsg
end
local StartTime = self.StartTime
local StopSeconds = self.StopSeconds
local Repeat = self.Repeat
local RandomizationFactor = self.RandomizationFactor
local RepeatSecondsInterval = self.RepeatSecondsInterval
local ScheduleID = self.ScheduleID
local Status, Result
if self.TimeEventObject then
Status, Result = xpcall( function() return self.TimeEventFunction( self.TimeEventObject, unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler )
else
Status, Result = xpcall( function() return self.TimeEventFunction( unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler )
end
self:T( { "Timer Event2 .. " .. self.ScheduleID, Status, Result, StartTime, RepeatSecondsInterval, RandomizationFactor, StopSeconds } )
if Status and ( ( Result == nil ) or ( Result and Result ~= false ) ) then
if Repeat and ( not StopSeconds or ( StopSeconds and timer.getTime() <= StartTime + StopSeconds ) ) then
local ScheduleTime =
timer.getTime() +
self.RepeatSecondsInterval +
math.random(
- ( RandomizationFactor * RepeatSecondsInterval / 2 ),
( RandomizationFactor * RepeatSecondsInterval / 2 )
) +
0.01
self:T( { self.TimeEventFunctionArguments, "Repeat:", timer.getTime(), ScheduleTime } )
return ScheduleTime -- returns the next time the function needs to be called.
else
timer.removeFunction( ScheduleID )
self.ScheduleID = nil
end
else
timer.removeFunction( ScheduleID )
self.ScheduleID = nil
end
return nil
end

View File

@ -0,0 +1,104 @@
--- This module contains the TIMER class.
--
-- ===
--
-- Takes care of scheduled function dispatching for defined in MOOSE classes.
--
-- ===
--
-- ===
--
-- ### Contributions: -
-- ### Authors: FlightControl : Design & Programming
--
-- @module Timer
--- The TIMER structure
-- @type TIMER
TIMER = {
ClassName = "TIMER",
CallID = 0,
}
function TIMER: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 nillified.
-- Nothing of this code should be modified without testing it thoroughly.
-- @param #TIMER self
-- @param Core.Scheduler#SCHEDULER Scheduler
function TIMER:AddSchedule( Scheduler )
self:F3( { Scheduler = Scheduler } )
-- Initialize the Functions 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.Schedulers = self.Schedulers or setmetatable( {}, { __mode = "k" } )
self.CallID = self.CallID + 1
self.Schedulers[Scheduler] = self.CallID
self:E(self.Schedulers)
self.Schedule = self.Schedule or setmetatable( {}, { __mode = "v" } )
self.Schedule[self.CallID] = {}
self.Schedule[self.CallID].ScheduleFunction = Scheduler.TimeEventFunction
self.Schedule[self.CallID].ScheduleArguments = Scheduler.TimeEventFunctionArguments
self.Schedule[self.CallID].ScheduleObject = Scheduler.TimeEventObject
self.Schedule[self.CallID].ScheduleStart = Scheduler.StartSeconds + .001
self:E( self.Schedule[self.CallID] )
local function ScheduleCallHandler( CallID )
local ErrorHandler = function( errmsg )
env.info( "Error in timer function: " .. errmsg )
if debug ~= nil then
env.info( debug.traceback() )
end
return errmsg
end
BASE:E( { self } )
local ScheduleFunction = self.Schedule[CallID].ScheduleFunction
local ScheduleArguments = self.Schedule[CallID].ScheduleArguments
local ScheduleObject = self.Schedule[CallID].ScheduleObject
local Status, Result
if ScheduleObject then
local function Timer()
return ScheduleFunction( ScheduleObject, unpack( ScheduleArguments ) )
end
Status, Result = xpcall( Timer, ErrorHandler )
else
local function Timer()
return ScheduleFunction( unpack( ScheduleArguments ) )
end
Status, Result = xpcall( Timer, ErrorHandler )
end
end
timer.scheduleFunction(
ScheduleCallHandler,
self.CallID,
timer.getTime() + 1
)
--[[
self:T( Schedule.FunctionID )
--]]
return self.CallID
end

View File

@ -7,6 +7,7 @@ Include.File( "Utilities/Utils" )
--- Core Classes
Include.File( "Core/Base" )
Include.File( "Core/Scheduler" )
Include.File( "Core/Timer")
Include.File( "Core/Event" )
Include.File( "Core/Menu" )
Include.File( "Core/Zone" )
@ -66,3 +67,6 @@ _EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT
--- Declare the main database object, which is used internally by the MOOSE classes.
_DATABASE = DATABASE:New() -- Database#DATABASE
--- Declare the timer dispatcher based on the TIMER class
_TIMERDISPATCHER = TIMER:New() -- Core.Timer#TIMER

View File

@ -148,9 +148,7 @@ function TASK_BASE:New( Mission, SetGroupAssign, TaskName, TaskType )
self:UnAssignFromUnit( TaskUnit )
self:MessageToGroups( TaskUnit:GetPlayerName() .. " aborted Task " .. self:GetName() )
end
if self:HasAliveUnits() == false then
self:__Abort( 1 )
end
self:__Abort( 1 )
end
end
)
@ -793,6 +791,27 @@ function TASK_BASE:SetBriefing( TaskBriefing )
return self
end
--- StateMachine callback function for a TASK
-- @param #TASK_BASE self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Core.Event#EVENTDATA Event
function TASK_BASE:onbeforeAbort( Event, From, To )
self:E("Abort")
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if self:HasAliveUnits() then
return false
end
end
self:MessageToGroups( "Task " .. self:GetName() .. " has been aborted! Task will be replanned." )
return true
end
--- StateMachine callback function for a TASK

View File

@ -0,0 +1,24 @@
--- Simple function scheduling
--
-- ===
--
-- Author: FlightControl
-- Date Created: 12 Dec 2016
--
-- # Situation
-- Uses the Tracing functions from BASE within the DCS.log file. Check the DCS.log file for the results.
-- Create a new SCHEDULER object.
-- Check the DCS.log.
--
-- # Test cases:
--
-- 1. The log should contain a "Hello World" line that is fired off 10 seconds after mission start.
--
--
-- # Status: TESTED - 12 Dec 2016
local TestScheduler = SCHEDULER:New( nil,
function()
BASE:E( "Hello World")
end, {}, 1
)

View File

@ -0,0 +1,34 @@
--- Simple Object Scheduling
--
-- ===
--
-- Author: FlightControl
-- Date Created: 12 Dec 2016
--
-- # Situation
-- Uses the Tracing functions from BASE within the DCS.log file. Check the DCS.log file for the results.
-- Create a new SCHEDULER object.
-- Check the DCS.log.
--
-- # Test cases:
--
-- 1. Tracing of a scheduler in an Object.
-- The log should contain a "Hello World" line of the object, that is fired off 1 seconds after mission start.
--
-- # Status: TESTED - 12 Dec 2016
local TEST_BASE = {
ClassName = "TEST_BASE",
}
function TEST_BASE:New( Message )
self = BASE:Inherit( self, BASE:New() )
local TestScheduler = SCHEDULER:New( self,
function( Object, Message )
Object:E( Message )
end, { Message }, 1
)
end
local Test = TEST_BASE:New( "Hello World" )

View File

@ -1,8 +0,0 @@
GroupTest = GROUP:FindByName("Test")
TestScheduler = SCHEDULER:New( nil,
function()
MESSAGE:New("Hello World", 5 ):ToAll()
end, {}, 10, 10 )

View File

@ -1,28 +0,0 @@
-- This test will schedule the same function 2 times.
SpawnTest = SPAWN:New( "Test" )
TestZone = ZONE:New( "TestZone" )
local function MessageTest2()
SpawnTest:SpawnInZone( TestZone, true )
end
local function MessageTest1()
SpawnTest:SpawnInZone( TestZone, true )
-- The second after 10 seconds
SCHEDULER:New( nil, MessageTest2, {}, 5 )
-- The third after 15 seconds
SCHEDULER:New( nil, MessageTest2, {}, 10 )
end
-- The first after 5 seconds
SCHEDULER:New( nil, MessageTest1, {}, 5 )
-- The fourth after 20 seconds
SCHEDULER:New( nil, MessageTest1, {}, 20 )

View File

@ -0,0 +1,56 @@
--- No Object Scheduling because of garbage collect and Object nillification.
--
-- ===
--
-- Author: FlightControl
-- Date Created: 12 Dec 2016
--
-- # Situation
-- Uses the Tracing functions from BASE within the DCS.log file. Check the DCS.log file for the results.
-- Create a new SCHEDULER object.
-- Check the DCS.log.
--
-- A Test object is created.
-- It is nillified directly after the Schedule has been planned.
-- There should be no schedule fired.
-- The Test object should be garbage collected!
--
-- THIS IS A VERY IMPORTANT TEST!
--
-- # Test cases:
--
-- 1. No schedule should be fired! The destructors of the Test object should be shown.
-- 2. Commend the nillification of the Test object in the source, and test again.
-- The schedule should now be fired and Hello World should be logged through the Test object.
--
-- # Status: STARTED - 12 Dec 2016
local TEST_BASE = {
ClassName = "TEST_BASE",
}
function TEST_BASE:New( Message )
self = BASE:Inherit( self, BASE:New() )
self.TestScheduler = SCHEDULER:New( self,
function( Object, Message )
Object:E( Message )
end, { Message }, 1
)
return self
end
do
local Test1 = TEST_BASE:New( "Hello World Test 1" )
end
local Test2 = TEST_BASE:New( "Hello World Test 2" )
Test2 = nil
local Test3 = TEST_BASE:New( "Hello World Test 3" )
collectgarbage()
BASE:E( "Collect Garbage executed." )
BASE:E( "You should only now see Test 3!" )
BASE:E( "Check if Test 1 and Test 2 are garbage collected!" )

View File

@ -1,16 +0,0 @@
-- This test will schedule the same function 2 times.
SpawnTest = SPAWN:New( "Test" )
TestZone = ZONE:New( "TestZone" )
local function MessageTest()
SpawnTest:SpawnInZone( TestZone, true )
end
-- The first after 5 seconds
TestScheduler1 = SCHEDULER:New( nil, MessageTest, {}, 5 )
-- The second after 10 seconds
TestScheduler2 = SCHEDULER:New( nil, MessageTest, {}, 10 )

View File

@ -31,6 +31,7 @@ Mission:AddScoring( Scoring )
-- Define the set of group of planes that can be assigned to the Mission object.
local SEADSet = SET_GROUP:New():FilterPrefixes( "Test SEAD"):FilterStart()
SEADSet:Flush()
-- Define the set of units that are the targets.
-- Note that I use FilterOnce, which means that the set will be defined only once,
@ -132,9 +133,11 @@ function FsmSEADTemplate:onenterUpdated( TaskUnit )
end
--local TaskSEAD2 = TASK_BASE:New( Mission, SEADSet, "SEAD Radars Vector 2", "SEAD" ) -- Tasking.Task#TASK_BASE
--TaskSEAD2:SetFsmTemplate( TaskSEAD:GetFsmTemplate():Copy() )
--Mission:AddTask( TaskSEAD2 )
local TaskSEAD2 = TASK_BASE:New( Mission, SEADSet, "SEAD Radars Vector 2", "SEAD" ) -- Tasking.Task#TASK_BASE
TaskSEAD2:SetFsmTemplate( TaskSEAD:GetFsmTemplate():Copy() )
Mission:AddTask( TaskSEAD2 )
Mission:RemoveTask(TaskSEAD)
TaskSEAD = nil
FsmSEADTemplate = nil