123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- -- 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)
- assert(idx)
- local i = 1
- while t[i] do
- if t[i]==train_id and t[i+1]==index then
- break
- end
- i = i + 2
- end
- t[i] = train_id
- t[i+1] = idx
- end
- function o.clear_all_items(train_id, pos)
- local t = occget(pos)
- if not t then return end
- local i = 1
- while t[i] do
- if t[i]==train_id then
- table.remove(t, i)
- table.remove(t, i)
- else
- i = i + 2
- end
- end
- end
- function o.clear_specific_item(train_id, pos, index)
- local t = occget(pos)
- if not t then return end
- local i = 1
- while t[i] do
- if t[i]==train_id and t[i+1]==index then
- table.remove(t, i)
- table.remove(t, i)
- else
- i = i + 2
- end
- 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 have a path item at this position
- -- Note that the case where 2 or more indices are at a position only occurs if there is a track loop.
- -- returns (table with train_id->{index1, index2...})
- function o.reverse_lookup(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
- if t[i]~=train_id then
- if not r[t[i]] then r[t[i]] = {} end
- table.insert(r[t[i]], t[i+1])
- end
- i = i + 2
- end
- return r
- end
- -- Gets a mapping of train id's to indexes of trains that have a path item at this position.
- -- Quick variant: will only return one index per train (the latest one added)
- -- returns (table with train_id->index)
- function o.reverse_lookup_quick(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
- r[t[i]] = t[i+1]
- i = i + 2
- end
- return r
- end
- local OCC_CLOSE_PROXIMITY = 3
- -- Gets a mapping of train id's to index of trains that have a path item at this position. Selects at most one index based on a given heuristic, or even none if it does not match the heuristic criterion
- -- returns (table with train_id->index), position
- -- "in_train": first index that lies between train index and end index
- -- "first_ahead": smallest index that is > current index
- -- "before_end"(default): smallest index that is > end index
- -- "close_proximity": within 3 indices close to the train index and end_index
- -- "any": just output the first index found and do not check further (also occurs if both "in_train" and "first_ahead" heuristics have failed
- function o.reverse_lookup_sel(pos, heuristic)
- if not heuristic then heuristic = "before_end" end
- local om = o.reverse_lookup(pos)
- local r = {}
- for tid, idxs in pairs(om) do
- r[tid] = idxs[1]
- if heuristic~="any" then
- --must run a heuristic
- --atdebug("reverse_lookup_sel is running heuristic for", pos,heuristic,"idxs",table.concat(idxs,","))
- local otrn = advtrains.trains[tid]
- advtrains.train_ensure_init(tid, otrn)
- local h_value
- for _,idx in ipairs(idxs) do
- if heuristic == "first_ahead" and idx > otrn.index and (not h_value or h_value>idx) then
- h_value = idx
- end
- if heuristic == "before_end" and idx > otrn.end_index and (not h_value or h_value>idx) then
- h_value = idx
- end
- if heuristic == "in_train" and idx < otrn.index and idx > otrn.end_index then
- h_value = idx
- end
- if heuristic == "close_proximity" and idx < (otrn.index + OCC_CLOSE_PROXIMITY) and idx > (otrn.end_index - OCC_CLOSE_PROXIMITY) then
- h_value = idx
- end
- end
- r[tid] = h_value
- --atdebug(h_value,"chosen")
- end
- 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)
- return o.reverse_lookup_sel(pos, "in_train")
- end
- advtrains.occ = o
|