Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
intrusivelist.qh
Go to the documentation of this file.
1#pragma once
2
3#include "bits.qh"
4#include "iter.qh"
5#include "test.qh"
6
11const int IL_MAX = 128;
12
14void IL_INIT(entity this);
16void IL_DTOR(entity this);
18void IL_ENDFRAME();
19
42
43// bitflags
44.vector il_lists;
45// bitflags
47
48#define IL_NEW() NEW(IntrusiveList)
49
50#define IL_EMPTY(this) (this.il_head == NULL)
51
52#define IL_FIRST(this) (this.il_head)
53#define IL_LAST(this) (this.il_tail)
54#define IL_PEEK(this) (this.il_tail)
55
58{
59 assert(this, return false);
60 return (it.(this.il_nextfld) || this.il_head == it || this.il_tail == it);
61}
62
66{
67 assert(this, return NULL);
68 assert(it, return NULL);
69 .entity il_next = this.il_nextfld;
70 .entity il_prev = this.il_prevfld;
71 assert(!IL_CONTAINS(this, it), return NULL);
72
73 entity tail = it.(il_prev) = this.il_tail;
74 if (tail)
75 tail.(il_next) = it;
76 else
77 this.il_head = it;
78 this.il_tail = it;
79 it.il_lists |= this.il_listmask;
80 return it;
81}
82
86{
87 assert(this, return NULL);
88 assert(it, return NULL);
89 .entity il_next = this.il_nextfld;
90 .entity il_prev = this.il_prevfld;
91 assert(!IL_CONTAINS(this, it), return NULL);
92
93 entity head = it.(il_next) = this.il_head;
94 if (head)
95 head.(il_prev) = it;
96 else
97 this.il_tail = it;
98 this.il_head = it;
99 it.il_lists |= this.il_listmask;
100 return it;
101}
102
106{
107 assert(this, return NULL);
108 .entity il_next = this.il_nextfld;
109 .entity il_prev = this.il_prevfld;
110
111 if (!this.il_tail)
112 return NULL;
113 entity it = this.il_tail;
114 entity prev = it.(il_prev);
115 if (prev)
116 (this.il_tail = prev).(il_next) = NULL;
117 else
118 this.il_head = this.il_tail = NULL;
119 if (this.il_loop_item == it)
120 this.il_loop_item = NULL;
121 it.(il_prev) = NULL;
122 return it;
123}
124
128{
129 assert(this, return NULL);
130 .entity il_next = this.il_nextfld;
131 .entity il_prev = this.il_prevfld;
132
133 if (!this.il_head)
134 return NULL;
135 entity it = this.il_head;
136 entity next = it.(il_next);
137 if (next)
138 (this.il_head = next).(il_prev) = NULL;
139 else
140 this.il_head = this.il_tail = NULL;
141 if (this.il_loop_item == it)
142 this.il_loop_item = it.(il_next);
143 it.(il_next) = NULL;
144 return it;
145}
146
150{
151 assert(this, return);
152 .entity il_next = this.il_nextfld;
153 .entity il_prev = this.il_prevfld;
154 //assert(!IL_CONTAINS(this, it), return);
155 entity next = it.(il_next);
156 entity prev = it.(il_prev);
157 entity ohead = this.il_head;
158 entity otail = this.il_tail;
159 if (next)
160 next.(il_prev) = prev;
161 else
162 this.il_tail = prev;
163 if (prev)
164 prev.(il_next) = next;
165 else
166 this.il_head = next;
167 LOG_DEBUGF("remove %i (%i :: %i), head: %i -> %i, tail: %i -> %i", it, it.(il_prev), it.(il_next), ohead, this.il_head, otail, this.il_tail);
168 if (this.il_loop_item == it)
169 this.il_loop_item = it.(il_next);
170 it.(il_next) = it.(il_prev) = NULL;
171}
172
174#define IL_CLEAR(this) MACRO_BEGIN \
175 IntrusiveList _il = this; \
176 assert(_il); \
177 .entity il_prev = _il.il_prevfld; \
178 .entity il_next = _il.il_nextfld; \
179 noref int i = 0; \
180 for (entity _next, _it = _il.il_head; _it; (_it = _next, ++i)) \
181 { \
182 _next = _it.(il_next); \
183 _it.(il_next) = _it.(il_prev) = NULL; \
184 } \
185 _il.il_head = _il.il_tail = NULL; \
186MACRO_END
187
189#define IL_DELETE(this) MACRO_BEGIN \
190 delete(this); \
191 this = NULL; \
192MACRO_END
193
194#define IL_EACH(this, cond, body) MACRO_BEGIN \
195 IntrusiveList _il = this; \
196 assert(_il); \
197 .entity il_next = _il.il_nextfld; \
198 noref int i = 0; \
199 entity il_loop_item_save = this.il_loop_item; \
200 this.il_loop_item = NULL; \
201 for (entity _next, _it = _il.il_head; _it; _it = _next, ++i) \
202 { \
203 const noref entity it = _it; \
204 this.il_loop_item = it; \
205 _next = it.(il_next); \
206 if (cond) \
207 LAMBDA(body) \
208 if (this.il_loop_item != it) /* current item removed? */ \
209 _next = this.il_loop_item; \
210 else \
211 _next = it.(il_next); /* in case next item has changed */ \
212 } \
213 this.il_loop_item = il_loop_item_save; \
214MACRO_END
215
216.int il_id;
220
221#define IL_FLOOR(n) ((n) | 0)
222#define IL_CEIL(n) IL_FLOOR((n) + 0.5)
223
224#define IL_LISTS_PER_BIT IL_CEIL(IL_MAX / (3 * 24))
225
228{
229 .entity nextfld, prevfld;
230 for (int i = il_links_ptr; i < il_links_ptr + IL_MAX; ++i)
231 {
232 int id = i;
233 if (id >= IL_MAX)
234 id -= IL_MAX;
235
236 if (!il_links[id])
237 {
238 il_links[id] = this;
239 int flds_idx = id * 2;
240 nextfld = il_links_flds[flds_idx + 0];
241 prevfld = il_links_flds[flds_idx + 1];
242 this.il_id = id;
243 int bit = IL_FLOOR(id / IL_LISTS_PER_BIT);
244 if (bit < 1 * 24)
245 this.il_listmask = '1 0 0' * BIT(bit - (0 * 24));
246 else if (bit < 2 * 24)
247 this.il_listmask = '0 1 0' * BIT(bit - (1 * 24));
248 else if (bit < 3 * 24)
249 this.il_listmask = '0 0 1' * BIT(bit - (2 * 24));
250 else
251 assert(false);
252 il_links_ptr = id + 1;
253 if (il_links_ptr >= IL_MAX)
255 this.il_nextfld = nextfld;
256 this.il_prevfld = prevfld;
257 return;
258 }
259 }
260 LOG_WARN("IntrusiveList overflow");
261}
262
265{
266 IL_CLEAR(this);
267 il_links[this.il_id] = NULL;
268}
269
272{
273#if 0
274 // incompatible with CSQC, remove() clears entities
275 for (int i = 0; i < IL_MAX; ++i)
276 {
277 IntrusiveList list = il_links[i];
278 if (list)
279 {
280 .entity nextfld = list.il_nextfld;
281 for (entity next, it = list.il_head; it; it = next)
282 {
283 next = it.(nextfld);
284 if (wasfreed(it))
285 IL_REMOVE(list, it);
286 }
287 }
288 }
289#endif
290}
291
295{
296 if (it.il_lists)
297 {
298 it.il_lists = '0 0 0';
299 for (int i = 0; i < IL_MAX * 2; ++i)
300 it.il_links_flds[i] = nil;
301 }
302}
303
306void ONREMOVE(entity this)
307{
308 // remove 'this' from any intrusive lists it is on
309 vector lists = this.il_lists;
310 if (lists)
311 for (int i = 0; i < IL_MAX; ++i)
312 {
313 IntrusiveList list = il_links[i];
314 if ((lists & list.il_listmask) && IL_CONTAINS(list, this))
315 IL_REMOVE(list, this);
316 }
317}
318
319
320#define IL_TEST_BUILD() s = il_test_build(il_test, ent1, ent2, ent3, ent4, ent5)
321
322string il_test_build(entity il_test, entity ent1, entity ent2, entity ent3, entity ent4, entity ent5)
323{
324 IL_CLEAR(il_test);
325 IL_PUSH(il_test, ent1);
326 IL_PUSH(il_test, ent2);
327 IL_PUSH(il_test, ent3);
328 IL_PUSH(il_test, ent4);
329 IL_PUSH(il_test, ent5);
330 return "";
331}
332
333TEST(intrusivelist, ModificationsWhileLooping)
334{
335 IntrusiveList il_test = IL_NEW();
336 entity ent1 = new(1), ent2 = new(2), ent3 = new(3), ent4 = new(4), ent5 = new(5);
337 string s;
338
340 IL_EACH(il_test, true,
341 {
342 s = strcat(s, it.classname);
343 if (it == ent2) IL_REMOVE(il_test, ent3);
344 if (it == ent4) IL_PUSH(il_test, ent3);
345 });
346 EXPECT_TRUE(s == "12453");
347
349 IL_EACH(il_test, true,
350 {
351 s = strcat(s, it.classname);
352 if (it == ent2) IL_REMOVE(il_test, ent2);
353 if (it == ent3) IL_REMOVE(il_test, ent3);
354 if (it == ent3) IL_REMOVE(il_test, ent4);
355 if (it == ent5) IL_POP(il_test);
356 });
357 EXPECT_TRUE(s == "1235");
358
360 IL_REMOVE(il_test, ent5);
361 IL_EACH(il_test, true,
362 {
363 s = strcat(s, it.classname);
364 if (it == ent1) IL_SHIFT(il_test);
365 if (it == ent4) IL_POP(il_test);
366 });
367 EXPECT_TRUE(s == "1234");
368
370 IL_EACH(il_test, true,
371 {
372 s = strcat(s, it.classname);
373 if (it == ent2)
374 IL_EACH(il_test, true,
375 {
376 s = strcat(s, it.classname);
377 if (it == ent3)
378 IL_EACH(il_test, true, s = strcat(s, it.classname));
379 if (it == ent4)
380 break;
381 });
382 });
383 EXPECT_TRUE(s == "12123123454345");
384
385 IL_DELETE(il_test);
386 delete(ent1); delete(ent2); delete(ent3); delete(ent4); delete(ent5);
387
388 SUCCEED();
389}
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition bits.qh:8
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
limitations: NULL cannot be present elements can only be present once a maximum of IL_MAX lists can e...
ATTRIB(IntrusiveList, il_head, entity)
prev
Definition all.qh:53
next
Definition all.qh:75
ERASEABLE void IL_DTOR(entity this)
ERASEABLE void IL_REMOVE(IntrusiveList this, entity it)
Remove any element, anywhere in the list.
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
#define IL_FLOOR(n)
vector il_lists
ERASEABLE bool IL_CONTAINS(IntrusiveList this, entity it)
#define IL_NEW()
ERASEABLE void IL_ENDFRAME()
void IL_REMOVE_RAW(entity it)
Clears any IL data from an entity (not an intrusive list).
ERASEABLE entity IL_POP(IntrusiveList this)
Pop from tail.
ERASEABLE void IL_INIT(entity this)
const int IL_MAX
Maximum amount of creatable lists.
#define IL_EACH(this, cond, body)
ERASEABLE entity IL_SHIFT(IntrusiveList this)
Pop from head.
entity il_links_flds[IL_MAX *2]
#define IL_CLEAR(this)
Remove all elements.
int il_id
#define IL_TEST_BUILD()
int il_links_ptr
vector il_listmask
IntrusiveList il_links[IL_MAX]
void ONREMOVE(entity this)
Called when an entity is deleted with delete() / remove() or when a player disconnects.
#define IL_DELETE(this)
Delete the list.
ERASEABLE entity IL_UNSHIFT(IntrusiveList this, entity it)
Push to head.
string il_test_build(entity il_test, entity ent1, entity ent2, entity ent3, entity ent4, entity ent5)
#define IL_LISTS_PER_BIT
#define ERASEABLE
Definition _all.inc:37
#define assert(expr,...)
Definition log.qh:8
#define LOG_DEBUGF(...)
Definition log.qh:79
#define LOG_WARN(...)
Definition log.qh:58
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
#define INIT(cname)
Definition oo.qh:214
#define CLASS(...)
Definition oo.qh:149
#define ENDCLASS(cname)
Definition oo.qh:286
#define DESTRUCTOR(cname)
Definition oo.qh:224
#define NULL
Definition post.qh:14
vector
Definition self.qh:96
#define TEST(suite, test)
Use UpperCamelCase for suite and test only.
Definition test.qh:6
#define SUCCEED()
Must be present at the end of a test.
Definition test.qh:17
#define EXPECT_TRUE(condition)
Definition test.qh:47