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
36
37// bitflags
38.vector il_lists;
39// bitflags
41
42#define IL_NEW() NEW(IntrusiveList)
43
44#define IL_EMPTY(this) (this.il_head == NULL)
45
46#define IL_FIRST(this) (this.il_head)
47#define IL_LAST(this) (this.il_tail)
48#define IL_PEEK(this) (this.il_tail)
49
52{
53 assert(this, return false);
54 return it.(this.il_nextfld) || this.il_head == it || this.il_tail == it;
55}
56
62{
63 assert(this, return NULL);
64 assert(it, return NULL);
65 .entity il_next = this.il_nextfld;
66 .entity il_prev = this.il_prevfld;
67 assert(!IL_CONTAINS(this, it), return NULL);
68
69 entity tail = it.(il_prev) = this.il_tail;
70 tail ? (tail.(il_next) = it) : this.il_head = it;
71 this.il_tail = it;
72 it.il_lists |= this.il_listmask;
73 return it;
74}
75
81{
82 assert(this, return NULL);
83 assert(it, return NULL);
84 .entity il_next = this.il_nextfld;
85 .entity il_prev = this.il_prevfld;
86 assert(!IL_CONTAINS(this, it), return NULL);
87
88 entity head = it.(il_next) = this.il_head;
89 head ? (head.(il_prev) = it) : this.il_tail = it;
90 this.il_head = it;
91 it.il_lists |= this.il_listmask;
92 return it;
93}
94
100{
101 assert(this, return NULL);
102 .entity il_next = this.il_nextfld;
103 .entity il_prev = this.il_prevfld;
104
105 if (!this.il_tail) return NULL;
106 entity it = this.il_tail;
107 entity prev = it.(il_prev);
108 if (prev) (this.il_tail = prev).(il_next) = NULL;
109 else this.il_head = this.il_tail = NULL;
110 if (this.il_loop_item == it)
111 this.il_loop_item = NULL;
112 it.(il_prev) = NULL;
113 return it;
114}
115
121{
122 assert(this, return NULL);
123 .entity il_next = this.il_nextfld;
124 .entity il_prev = this.il_prevfld;
125
126 if (!this.il_head) return NULL;
127 entity it = this.il_head;
128 entity next = it.(il_next);
129 if (next) (this.il_head = next).(il_prev) = NULL;
130 else this.il_head = this.il_tail = NULL;
131 if (this.il_loop_item == it)
132 this.il_loop_item = it.(il_next);
133 it.(il_next) = NULL;
134 return it;
135}
136
142{
143 assert(this, return);
144 .entity il_next = this.il_nextfld;
145 .entity il_prev = this.il_prevfld;
146 //assert(!IL_CONTAINS(this, it), return);
147 entity next = it.(il_next);
148 entity prev = it.(il_prev);
149 entity ohead = this.il_head;
150 entity otail = this.il_tail;
151 next ? next.(il_prev) = prev : this.il_tail = prev;
152 prev ? prev.(il_next) = next : this.il_head = next;
153 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);
154 if (this.il_loop_item == it)
155 this.il_loop_item = it.(il_next);
156 it.(il_next) = it.(il_prev) = NULL;
157}
158
162#define IL_CLEAR(this) \
163 MACRO_BEGIN \
164 IntrusiveList _il = this; \
165 assert(_il); \
166 .entity il_prev = _il.il_prevfld; \
167 .entity il_next = _il.il_nextfld; \
168 noref int i = 0; \
169 for (entity _next, _it = _il.il_head; _it; (_it = _next, ++i)) \
170 { \
171 _next = _it.(il_next); \
172 _it.(il_next) = _it.(il_prev) = NULL; \
173 } \
174 _il.il_head = _il.il_tail = NULL; \
175 MACRO_END
176
180#define IL_DELETE(this) \
181 MACRO_BEGIN \
182 delete(this); \
183 this = NULL; \
184 MACRO_END
185
186#define IL_EACH(this, cond, body) \
187 MACRO_BEGIN \
188 IntrusiveList _il = this; \
189 assert(_il); \
190 .entity il_next = _il.il_nextfld; \
191 noref int i = 0; \
192 entity il_loop_item_save = this.il_loop_item; \
193 this.il_loop_item = NULL; \
194 for (entity _next, _it = _il.il_head; _it; (_it = _next, ++i)) \
195 { \
196 const noref entity it = _it; \
197 this.il_loop_item = it; \
198 _next = it.(il_next); \
199 if (cond) { LAMBDA(body) } \
200 if (this.il_loop_item != it) /* current item removed? */ \
201 _next = this.il_loop_item; \
202 else \
203 _next = it.(il_next); /* in case next item has changed */ \
204 } \
205 this.il_loop_item = il_loop_item_save; \
206 MACRO_END
207
208.int il_id;
212
213#define IL_FLOOR(n) ((n) | 0)
214#define IL_CEIL(n) IL_FLOOR((n) + 0.5)
215
216#define IL_LISTS_PER_BIT IL_CEIL(IL_MAX / (3 * 24))
217
220{
221 .entity nextfld, prevfld;
222 for (int i = il_links_ptr; i < il_links_ptr + IL_MAX; ++i) {
223 int id = i;
224 if (id >= IL_MAX) id -= IL_MAX;
225
226 if (!il_links[id]) {
227 il_links[id] = this;
228 int flds_idx = id * 2;
229 nextfld = il_links_flds[flds_idx + 0];
230 prevfld = il_links_flds[flds_idx + 1];
231 this.il_id = id;
232 int bit = IL_FLOOR(id / IL_LISTS_PER_BIT);
233 if (bit < (1 * 24)) this.il_listmask = '1 0 0' * BIT(bit - (0 * 24));
234 else if (bit < (2 * 24)) this.il_listmask = '0 1 0' * BIT(bit - (1 * 24));
235 else if (bit < (3 * 24)) this.il_listmask = '0 0 1' * BIT(bit - (2 * 24));
236 else assert(false);
237 il_links_ptr = id + 1;
239 this.il_nextfld = nextfld;
240 this.il_prevfld = prevfld;
241 return;
242 }
243 }
244 LOG_WARN("IntrusiveList overflow");
245}
246
249{
250 IL_CLEAR(this);
251 il_links[this.il_id] = NULL;
252}
253
256{
257#if 0
258 // incompatible with CSQC, remove() clears entities
259 for (int i = 0; i < IL_MAX; ++i) {
260 IntrusiveList list = il_links[i];
261 if (list) {
262 .entity nextfld = list.il_nextfld;
263 for (entity next, it = list.il_head; it; it = next) {
264 next = it.(nextfld);
265 if (wasfreed(it)) {
266 IL_REMOVE(list, it);
267 }
268 }
269 }
270 }
271#endif
272}
273
274// clears any IL data from an entity (not an intrusive list)
275// it should be used only in very particular cases such as after a copyentity call
277{
278 if (it.il_lists)
279 {
280 it.il_lists = '0 0 0';
281 for (int i = 0; i < IL_MAX * 2; ++i)
282 it.il_links_flds[i] = nil;
283 }
284}
285
286// called when an entity is deleted with delete() / remove()
287// or when a player disconnects
288void ONREMOVE(entity this)
289{
290 // remove 'this' from any intrusive lists it is on
291 vector lists = this.il_lists;
292 if (lists) {
293 for (int i = 0; i < IL_MAX; ++i) {
294 IntrusiveList list = il_links[i];
295 if ((lists & list.il_listmask) && IL_CONTAINS(list, this)) {
296 IL_REMOVE(list, this);
297 }
298 }
299 }
300}
301
302
303#define IL_TEST_BUILD() s = il_test_build(il_test, ent1, ent2, ent3, ent4, ent5)
304
305string il_test_build(entity il_test, entity ent1, entity ent2, entity ent3, entity ent4, entity ent5)
306{
307 IL_CLEAR(il_test);
308 IL_PUSH(il_test, ent1);
309 IL_PUSH(il_test, ent2);
310 IL_PUSH(il_test, ent3);
311 IL_PUSH(il_test, ent4);
312 IL_PUSH(il_test, ent5);
313 return "";
314}
315
316TEST(intrusivelist, ModificationsWhileLooping)
317{
318 IntrusiveList il_test = IL_NEW();
319 entity ent1 = new(1), ent2 = new(2), ent3 = new(3), ent4 = new(4), ent5 = new(5);
320 string s;
321
323 IL_EACH(il_test, true,
324 {
325 s = strcat(s, it.classname);
326 if (it == ent2) IL_REMOVE(il_test, ent3);
327 if (it == ent4) IL_PUSH(il_test, ent3);
328 });
329 EXPECT_TRUE(s == "12453");
330
332 IL_EACH(il_test, true,
333 {
334 s = strcat(s, it.classname);
335 if (it == ent2) IL_REMOVE(il_test, ent2);
336 if (it == ent3) IL_REMOVE(il_test, ent3);
337 if (it == ent3) IL_REMOVE(il_test, ent4);
338 if (it == ent5) IL_POP(il_test);
339 });
340 EXPECT_TRUE(s == "1235");
341
343 IL_REMOVE(il_test, ent5);
344 IL_EACH(il_test, true,
345 {
346 s = strcat(s, it.classname);
347 if (it == ent1) IL_SHIFT(il_test);
348 if (it == ent4) IL_POP(il_test);
349 });
350 EXPECT_TRUE(s == "1234");
351
353 IL_EACH(il_test, true,
354 {
355 s = strcat(s, it.classname);
356 if (it == ent2)
357 IL_EACH(il_test, true,
358 {
359 s = strcat(s, it.classname);
360 if (it == ent3)
361 IL_EACH(il_test, true,
362 {
363 s = strcat(s, it.classname);
364 });
365 if (it == ent4)
366 break;
367 });
368 });
369 EXPECT_TRUE(s == "12123123454345");
370
371 IL_DELETE(il_test);
372 delete(ent1); delete(ent2); delete(ent3); delete(ent4); delete(ent5);
373
374 SUCCEED();
375}
#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:71
next
Definition all.qh:93
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)
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)
#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:81
#define LOG_WARN(...)
Definition log.qh:61
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
#define INIT(cname)
Definition oo.qh:210
#define CLASS(...)
Definition oo.qh:145
#define ENDCLASS(cname)
Definition oo.qh:281
#define DESTRUCTOR(cname)
Definition oo.qh:220
#define NULL
Definition post.qh:14
vector
Definition self.qh:92
#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:15
#define EXPECT_TRUE(condition)
Definition test.qh:46