-- occupation.lua --[[ Collects and manages positions where trains occupy and/or reserve/require space It turned out that, especially for the TSS, some more, even overlapping zones are required. Packing those into a data structure would just become a huge mess! Instead, this occupation system will store the path indices of positions in the corresponding. train's paths. So, the occupation is a reverse lookup of paths. Then, a callback system will handle changes in those indices, as follows: Whenever the train generates new path items (path_get/path_create), their counterpart indices will be filled in here. Whenever a path gets invalidated or path items are deleted, their index counterpart is erased from here. When a train needs to know whether a position is blocked by another train, it will (and is permitted to) query the train.index and train.end_index and compare them to the blocked position's index. Callback system for 3rd-party path checkers: advtrains.te_register_on_new_path(func(id, train)) -- Called when a train's path is re-initalized, either when it was invalidated -- or the saves were just loaded -- It can be assumed that everything is in the state of when the last run -- of on_update was made, but all indices are shifted by an unknown amount. advtrains.te_register_on_update(func(id, train)) -- Called each step and after a train moved, its length changed or some other event occured -- The path is unmodified, and train.index and train.end_index can be reliably -- queried for the new position and length of the train. -- note that this function might be called multiple times per step, and this -- function being called does not necessarily mean that something has changed. -- It is ensured that on_new_path callbacks are executed prior to these callbacks whenever -- an invalidation or a reload occured. advtrains.te_register_on_create(func(id, train)) -- Called right after a train is created, right after the initial new_path callback advtrains.te_register_on_remove(func(id, train)) -- Called right before a train is deleted All callbacks are allowed to save certain values inside the train table, but they must ensure that those are reinitialized in the on_new_path callback. The on_new_path callback must explicitly set ALL OF those values to nil or to a new updated value, and must not rely on their existence. ]]-- local o = {} local occ = {} local occ_chg = {} local function occget(p) local t = occ[p.y] if not t then occ[p.y] = {} t = occ[p.y] end local s = t t = t[p.x] if not t then s[p.x] = {} t = s[p.x] end return t[p.z] end local function occgetcreate(p) local t = occ[p.y] if not t then occ[p.y] = {} t = occ[p.y] end local s = t t = t[p.x] if not t then s[p.x] = {} t = s[p.x] end s = t t = t[p.z] if not t then s[p.z] = {} t = s[p.z] end return t end function o.set_item(train_id, pos, idx) local t = occgetcreate(pos) local i = 1 while t[i] do if t[i]==train_id then break end i = i + 2 end t[i] = train_id t[i+1] = idx end function o.clear_item(train_id, pos) local t = occget(pos) if not t then return end local i = 1 local moving = false while t[i] do if t[i]==train_id then if moving then -- if, for some occasion, there should be a duplicate entry, erase this one too atwarn("Duplicate occupation entry at",pos,"for train",train_id,":",t) i = i - 2 end moving = true end if moving then t[i] = t[i+2] t[i+1] = t[i+3] end i = i + 2 end end -- Checks whether some other train (apart from train_id) has it's 0 zone here function o.check_collision(pos, train_id) local npos = advtrains.round_vector_floor_y(pos) local t = occget(npos) if not t then return end local i = 1 while t[i] do local ti = t[i] if ti~=train_id then local idx = t[i+1] local train = advtrains.trains[ti] --atdebug("checking train",t[i],"index",idx,"<>",train.index,train.end_index) if train and idx >= train.end_index and idx <= train.index then --atdebug("collides.") return train -- return train it collided with so we can couple when shunting is enabled end end i = i + 2 end return false end -- Gets a mapping of train id's to indexes of trains that share this path item with this train -- The train itself will not be included. -- If the requested index position is off-track, returns {}. -- returns (table with train_id->index), position function o.get_occupations(train, index) local ppos, ontrack = advtrains.path_get(train, index) if not ontrack then atlog("Train",train.id,"get_occupations requested off-track",index) return {}, ppos end local pos = advtrains.round_vector_floor_y(ppos) local t = occget(pos) if not t then return {} end local r = {} local i = 1 local train_id = train.id while t[i] do if t[i]~=train_id then r[t[i]] = t[i+1] end i = i + 2 end return r, pos end -- Gets a mapping of train id's to indexes of trains that stand or drive over -- returns (table with train_id->index) function o.get_trains_at(ppos) local pos = advtrains.round_vector_floor_y(ppos) local t = occget(pos) if not t then return {} end local r = {} local i = 1 while t[i] do local train = advtrains.trains[t[i]] local idx = t[i+1] if train.end_index - 0.5 <= idx and idx <= train.index + 0.5 then r[t[i]] = idx end i = i + 2 end return r end -- Gets a mapping of train id's to indexes of trains that have a path -- generated over this node -- returns (table with train_id->index) function o.get_trains_over(ppos) local pos = advtrains.round_vector_floor_y(ppos) local t = occget(pos) if not t then return {} end local r = {} local i = 1 while t[i] do local idx = t[i+1] r[t[i]] = idx i = i + 2 end return r end advtrains.occ = o