From 7b488f40d95c2d68db898d7cb228e17e001cea73 Mon Sep 17 00:00:00 2001
From: orwell96 <orwell@bleipb.de>
Date: Mon, 26 Aug 2019 23:08:02 +0200
Subject: Add lines scheduler for reliable railway-time scheduling(which is
 also safer than the atlatc scheduler) and document new atlatc functions

---
 advtrains_line_automation/init.lua      |   6 +-
 advtrains_line_automation/scheduler.lua | 104 ++++++++++++++++++++++++++++++++
 2 files changed, 109 insertions(+), 1 deletion(-)
 create mode 100644 advtrains_line_automation/scheduler.lua

(limited to 'advtrains_line_automation')

diff --git a/advtrains_line_automation/init.lua b/advtrains_line_automation/init.lua
index 0f4a4eb..7b758bc 100644
--- a/advtrains_line_automation/init.lua
+++ b/advtrains_line_automation/init.lua
@@ -19,6 +19,7 @@ advtrains.lines = {
 local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELIM
 
 dofile(modpath.."railwaytime.lua")
+dofile(modpath.."scheduler.lua")
 dofile(modpath.."stoprail.lua")
 
 
@@ -27,6 +28,7 @@ function advtrains.lines.load(data)
 		advtrains.lines.stations = data.stations or {}
 		advtrains.lines.stops = data.stops or {}
 		advtrains.lines.rwt.set_time(data.rwt_time)
+		advtrains.lines.sched.load(data.scheduler_queue)
 	end
 end
 
@@ -34,10 +36,12 @@ function advtrains.lines.save()
 	return {
 		stations = advtrains.lines.stations,
 		stops = advtrains.lines.stops,
-		rwt_time = advtrains.lines.rwt.get_time()
+		rwt_time = advtrains.lines.rwt.get_time(),
+		scheduler_queue = advtrains.lines.sched.save()
 	}
 end
 
 function advtrains.lines.step(dtime)
 	advtrains.lines.rwt.step(dtime)
+	advtrains.lines.sched.run()
 end
diff --git a/advtrains_line_automation/scheduler.lua b/advtrains_line_automation/scheduler.lua
new file mode 100644
index 0000000..da639df
--- /dev/null
+++ b/advtrains_line_automation/scheduler.lua
@@ -0,0 +1,104 @@
+-- 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 = {}
+--Function signature is function(d)
+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
+	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("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
+
+function sched.enqueue(rwtime, handler, evtdata, unitid, unitlim)
+	local qtime = ln.rwt.to_secs(rwtime)
+	
+	local cnt=1
+	local ucn, elem
+	
+	ucn = (units_cnt[unitid] or 0)
+	local ulim=(unitlim or UNITS_THRESH)
+	if ucn >= ulim then
+		atwarn("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
+
+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
-- 
cgit v1.2.3