1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
-- scheduler.lua
-- Implementation of a Railway time schedule queue
-- In contrast to the LuaATC interrupt queue, this one can handle many different
-- event receivers. This is done by registering a callback with the scheduler
local ln = advtrains.lines
local sched = {}
local UNITS_THRESH = 10
local MAX_PER_ITER = 10
local callbacks = {}
-- Register a handler callback to handle scheduler items.
-- e - a handler identifier (corresponds to "handler" in enqueue() )
-- func - a function(evtdata) to be executed when a schedule item expires
-- evtdata - arbitrary data that has been passed into enqueue()
function sched.register_callback(e, func)
callbacks[e] = func
end
--[[
{
t = <railway time in seconds>
e = <handler callback>
d = <data table>
u = <unit identifier>
}
The "unit identifier" is there to prevent schedule overflows. It can be, for example, the position hash
of a node or a train ID. If the number of schedules for a unit exceeds UNITS_THRESH, further schedules are
blocked.
]]--
local queue = {}
local units_cnt = {}
function sched.load(data)
if data then
for i,elem in ipairs(data) do
table.insert(queue, elem)
units_cnt[elem.u] = (units_cnt[elem.u] or 0) + 1
end
atlog("[lines][scheduler] Loaded the schedule queue,",#data,"items.")
end
end
function sched.save()
return queue
end
function sched.run()
local ctime = ln.rwt.get_time()
local cnt = 0
local ucn, elem
while cnt <= MAX_PER_ITER do
elem = queue[1]
if elem and elem.t <= ctime then
table.remove(queue, 1)
if callbacks[elem.e] then
-- run it
callbacks[elem.e](elem.d)
else
atwarn("[lines][scheduler] No callback to handle schedule",elem)
end
cnt=cnt+1
ucn = units_cnt[elem.u]
if ucn and ucn>0 then
units_cnt[elem.u] = ucn - 1
end
else
break
end
end
end
-- Enqueue a new scheduled item to be executed at "rwtime"
-- handler: a string identifying the handler to use (registered with sched.register_callback())
-- evtdata: Arbitrary Lua data to be passed to the handler callback
-- unitid: An arbitrary string uniquely identifying the thing that is issuing this enqueue.
-- used to prevent expotentially growing "scheduler bombs"
-- unitlim: Custom override for UNITS_THRESH (see there)
function sched.enqueue(rwtime, handler, evtdata, unitid, unitlim)
local qtime = ln.rwt.to_secs(rwtime)
assert(type(handler)=="string")
assert(type(unitid)=="string")
assert(type(unitlim)=="number")
local cnt=1
local ucn, elem
ucn = (units_cnt[unitid] or 0)
local ulim=(unitlim or UNITS_THRESH)
if ucn >= ulim then
atwarn("[lines][scheduler] discarding enqueue for",handler,"(limit",ulim,") because unit",unitid,"has already",ucn,"schedules enqueued")
return false
end
while true do
elem = queue[cnt]
if not elem or elem.t > qtime then
table.insert(queue, cnt, {
t=qtime,
e=handler,
d=evtdata,
u=unitid,
})
units_cnt[unitid] = ucn + 1
return true
end
cnt = cnt+1
end
end
-- See enqueue(). Same meaning, except that rwtime is relative to now.
function sched.enqueue_in(rwtime, handler, evtdata, unitid, unitlim)
local ctime = ln.rwt.get_time()
sched.enqueue(ctime + rwtime, handler, evtdata, unitid, unitlim)
end
ln.sched = sched
|