occupation.lua 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. -- occupation.lua
  2. --[[
  3. Collects and manages positions where trains occupy and/or reserve/require space
  4. It turned out that, especially for the TSS, some more, even overlapping zones are required.
  5. Packing those into a data structure would just become a huge mess!
  6. Instead, this occupation system will store the path indices of positions in the corresponding.
  7. train's paths.
  8. So, the occupation is a reverse lookup of paths.
  9. Then, a callback system will handle changes in those indices, as follows:
  10. Whenever the train generates new path items (path_get/path_create), their counterpart indices will be filled in here.
  11. Whenever a path gets invalidated or path items are deleted, their index counterpart is erased from here.
  12. When a train needs to know whether a position is blocked by another train, it will (and is permitted to)
  13. query the train.index and train.end_index and compare them to the blocked position's index.
  14. Callback system for 3rd-party path checkers:
  15. advtrains.te_register_on_new_path(func(id, train))
  16. -- Called when a train's path is re-initalized, either when it was invalidated
  17. -- or the saves were just loaded
  18. -- It can be assumed that everything is in the state of when the last run
  19. -- of on_update was made, but all indices are shifted by an unknown amount.
  20. advtrains.te_register_on_update(func(id, train))
  21. -- Called each step and after a train moved, its length changed or some other event occured
  22. -- The path is unmodified, and train.index and train.end_index can be reliably
  23. -- queried for the new position and length of the train.
  24. -- note that this function might be called multiple times per step, and this
  25. -- function being called does not necessarily mean that something has changed.
  26. -- It is ensured that on_new_path callbacks are executed prior to these callbacks whenever
  27. -- an invalidation or a reload occured.
  28. advtrains.te_register_on_create(func(id, train))
  29. -- Called right after a train is created, right after the initial new_path callback
  30. advtrains.te_register_on_remove(func(id, train))
  31. -- Called right before a train is deleted
  32. All callbacks are allowed to save certain values inside the train table, but they must ensure that
  33. those are reinitialized in the on_new_path callback. The on_new_path callback must explicitly
  34. set ALL OF those values to nil or to a new updated value, and must not rely on their existence.
  35. ]]--
  36. local o = {}
  37. local occ = {}
  38. local occ_chg = {}
  39. local function occget(p)
  40. local t = occ[p.y]
  41. if not t then
  42. occ[p.y] = {}
  43. t = occ[p.y]
  44. end
  45. local s = t
  46. t = t[p.x]
  47. if not t then
  48. s[p.x] = {}
  49. t = s[p.x]
  50. end
  51. return t[p.z]
  52. end
  53. local function occgetcreate(p)
  54. local t = occ[p.y]
  55. if not t then
  56. occ[p.y] = {}
  57. t = occ[p.y]
  58. end
  59. local s = t
  60. t = t[p.x]
  61. if not t then
  62. s[p.x] = {}
  63. t = s[p.x]
  64. end
  65. s = t
  66. t = t[p.z]
  67. if not t then
  68. s[p.z] = {}
  69. t = s[p.z]
  70. end
  71. return t
  72. end
  73. function o.set_item(train_id, pos, idx)
  74. local t = occgetcreate(pos)
  75. assert(idx)
  76. local i = 1
  77. while t[i] do
  78. if t[i]==train_id and t[i+1]==index then
  79. break
  80. end
  81. i = i + 2
  82. end
  83. t[i] = train_id
  84. t[i+1] = idx
  85. end
  86. function o.clear_all_items(train_id, pos)
  87. local t = occget(pos)
  88. if not t then return end
  89. local i = 1
  90. while t[i] do
  91. if t[i]==train_id then
  92. table.remove(t, i)
  93. table.remove(t, i)
  94. else
  95. i = i + 2
  96. end
  97. end
  98. end
  99. function o.clear_specific_item(train_id, pos, index)
  100. local t = occget(pos)
  101. if not t then return end
  102. local i = 1
  103. while t[i] do
  104. if t[i]==train_id and t[i+1]==index then
  105. table.remove(t, i)
  106. table.remove(t, i)
  107. else
  108. i = i + 2
  109. end
  110. end
  111. end
  112. -- Checks whether some other train (apart from train_id) has it's 0 zone here
  113. function o.check_collision(pos, train_id)
  114. local npos = advtrains.round_vector_floor_y(pos)
  115. local t = occget(npos)
  116. if not t then return end
  117. local i = 1
  118. while t[i] do
  119. local ti = t[i]
  120. if ti~=train_id then
  121. local idx = t[i+1]
  122. local train = advtrains.trains[ti]
  123. --atdebug("checking train",t[i],"index",idx,"<>",train.index,train.end_index)
  124. if train and idx >= train.end_index and idx <= train.index then
  125. --atdebug("collides.")
  126. return train -- return train it collided with so we can couple when shunting is enabled
  127. end
  128. end
  129. i = i + 2
  130. end
  131. return false
  132. end
  133. -- Gets a mapping of train id's to indexes of trains that have a path item at this position
  134. -- Note that the case where 2 or more indices are at a position only occurs if there is a track loop.
  135. -- returns (table with train_id->{index1, index2...})
  136. function o.reverse_lookup(ppos)
  137. local pos = advtrains.round_vector_floor_y(ppos)
  138. local t = occget(pos)
  139. if not t then return {} end
  140. local r = {}
  141. local i = 1
  142. while t[i] do
  143. if t[i]~=train_id then
  144. if not r[t[i]] then r[t[i]] = {} end
  145. table.insert(r[t[i]], t[i+1])
  146. end
  147. i = i + 2
  148. end
  149. return r
  150. end
  151. -- Gets a mapping of train id's to indexes of trains that have a path item at this position.
  152. -- Quick variant: will only return one index per train (the latest one added)
  153. -- returns (table with train_id->index)
  154. function o.reverse_lookup_quick(ppos)
  155. local pos = advtrains.round_vector_floor_y(ppos)
  156. local t = occget(pos)
  157. if not t then return {} end
  158. local r = {}
  159. local i = 1
  160. while t[i] do
  161. r[t[i]] = t[i+1]
  162. i = i + 2
  163. end
  164. return r
  165. end
  166. local OCC_CLOSE_PROXIMITY = 3
  167. -- 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
  168. -- returns (table with train_id->index), position
  169. -- "in_train": first index that lies between train index and end index
  170. -- "first_ahead": smallest index that is > current index
  171. -- "before_end"(default): smallest index that is > end index
  172. -- "close_proximity": within 3 indices close to the train index and end_index
  173. -- "any": just output the first index found and do not check further (also occurs if both "in_train" and "first_ahead" heuristics have failed
  174. function o.reverse_lookup_sel(pos, heuristic)
  175. if not heuristic then heuristic = "before_end" end
  176. local om = o.reverse_lookup(pos)
  177. local r = {}
  178. for tid, idxs in pairs(om) do
  179. r[tid] = idxs[1]
  180. if heuristic~="any" then
  181. --must run a heuristic
  182. --atdebug("reverse_lookup_sel is running heuristic for", pos,heuristic,"idxs",table.concat(idxs,","))
  183. local otrn = advtrains.trains[tid]
  184. advtrains.train_ensure_init(tid, otrn)
  185. local h_value
  186. for _,idx in ipairs(idxs) do
  187. if heuristic == "first_ahead" and idx > otrn.index and (not h_value or h_value>idx) then
  188. h_value = idx
  189. end
  190. if heuristic == "before_end" and idx > otrn.end_index and (not h_value or h_value>idx) then
  191. h_value = idx
  192. end
  193. if heuristic == "in_train" and idx < otrn.index and idx > otrn.end_index then
  194. h_value = idx
  195. end
  196. if heuristic == "close_proximity" and idx < (otrn.index + OCC_CLOSE_PROXIMITY) and idx > (otrn.end_index - OCC_CLOSE_PROXIMITY) then
  197. h_value = idx
  198. end
  199. end
  200. r[tid] = h_value
  201. --atdebug(h_value,"chosen")
  202. end
  203. end
  204. return r, pos
  205. end
  206. -- Gets a mapping of train id's to indexes of trains that stand or drive over
  207. -- returns (table with train_id->index)
  208. function o.get_trains_at(ppos)
  209. local pos = advtrains.round_vector_floor_y(ppos)
  210. return o.reverse_lookup_sel(pos, "in_train")
  211. end
  212. advtrains.occ = o