setorigin(debugmodel_entity, view_origin);
debugmodel_entity.angles = view_angles;
debugmodel_entity.draw = DrawDebugModel;
+ IL_PUSH(g_drawables, debugmodel_entity);
return;
}
draw_teamradar_background(hud_panel_radar_foreground_alpha);
- FOREACH_ENTITY_CLASS("radarlink", true, draw_teamradar_link(it.origin, it.velocity, it.team));
+ IL_EACH(g_radarlinks, true, draw_teamradar_link(it.origin, it.velocity, it.team));
FOREACH_ENTITY_FLAGS(teamradar_icon, 0xFFFFFF, {
if ( hud_panel_radar_mouse )
this.drawmask = MASK_NORMAL;
//this.move_movetype = MOVETYPE_NOCLIP;
//this.draw = Spawn_Draw;
+ IL_PUSH(g_drawables, this);
}*/
if(autocvar_cl_spawn_point_particles)
{
else { this.cnt = particleeffectnum(EFFECT_SPAWNPOINT_NEUTRAL); }
this.draw = Spawn_Draw;
+ if (is_new) IL_PUSH(g_drawables, this);
setpredraw(this, Spawn_PreDraw);
this.fade_start = autocvar_cl_spawn_point_dist_min;
this.fade_end = autocvar_cl_spawn_point_dist_max;
.float eliminated;
.void(entity) draw;
+IntrusiveList g_drawables;
+STATIC_INIT(g_drawables) { g_drawables = IL_NEW(); }
.void(entity) draw2d;
+IntrusiveList g_drawables_2d;
+STATIC_INIT(g_drawables_2d) { g_drawables_2d = IL_NEW(); }
.void(entity) entremove;
float drawframetime;
vector view_origin, view_forward, view_right, view_up;
+IntrusiveList g_radarlinks;
+STATIC_INIT(g_radarlinks) { g_radarlinks = IL_NEW(); }
+
bool button_zoom;
bool spectatorbutton_zoom;
bool button_attack2;
this.iflags = IFLAG_VELOCITY | IFLAG_ORIGIN;
this.classname = "radarlink";
+ if (isnew) IL_PUSH(g_radarlinks, this);
if(sendflags & 1)
{
{
entity e = new_pure(porto);
e.draw = Porto_Draw;
+ IL_PUSH(g_drawables, e);
e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
}
mousepos = mousepos*0.5 + getmousepos();
*/
- FOREACH_ENTITY_FLOAT(pure_data, false,
- {
- if(it.draw) { it.draw(it); }
- });
+ IL_EACH(g_drawables, it.draw, it.draw(it));
addentities(MASK_NORMAL | MASK_ENGINE | MASK_ENGINEVIEWMODELS);
renderscene();
} else */
// draw 2D entities
- FOREACH_ENTITY_FLOAT(pure_data, false,
- {
- if(it.draw2d) { it.draw2d(it); }
- });
+ IL_EACH(g_drawables_2d, it.draw2d, it.draw2d(it));
Draw_ShowNames_All();
Debug_Draw();
// let's reset the view back to normal for the end
setproperty(VF_MIN, '0 0 0');
setproperty(VF_SIZE, '1 0 0' * w + '0 1 0' * h);
+
+ IL_ENDFRAME();
}
this.entremove = Ent_Wall_Remove;
this.draw = Ent_Wall_Draw;
+ if (isnew) IL_PUSH(g_drawables, this);
setpredraw(this, Ent_Wall_PreDraw);
}
this.classname = "csqcprojectile";
this.draw = Projectile_Draw;
+ if (isnew) IL_PUSH(g_drawables, this);
this.entremove = Ent_RemoveProjectile;
}
{
entity e = TRACE_ENT = new_pure(TRACE_ENT);
e.draw2d = Trace_draw2d;
+ IL_PUSH(g_drawables_2d, e);
}
#endif
casing.drawmask = MASK_NORMAL;
casing.draw = Casing_Draw;
+ if (isNew) IL_PUSH(g_drawables, casing);
casing.velocity = casing.velocity + 2 * prandomvec();
casing.avelocity = '0 250 0' + 100 * prandomvec();
casing.move_movetype = MOVETYPE_BOUNCE;
setsize (gib, '-8 -8 -8', '8 8 8');
gib.draw = Gib_Draw;
+ IL_PUSH(g_drawables, gib);
if(destroyontouch)
settouch(gib, Gib_Touch);
else
e.cnt = ReadByte() / 255.0; // actually alpha
e.draw = ModelEffect_Draw;
+ if (isnew) IL_PUSH(g_drawables, e);
if (!isnew) remove(e); // yes, this IS stupid, but I don't need to duplicate all the read* stuff then
return true;
e.move_time = time + 0.05;
e.drawmask = MASK_NORMAL;
e.draw = ons_generator_ray_draw;
+ IL_PUSH(g_drawables, e);
}
void generator_draw(entity this)
this.m_group = _group;
this.m_friendlyfire = _friendlyfire;
DamageText_update(this, _origin, _health, _armor, _deathtype);
+ IL_PUSH(g_drawables_2d, this);
}
ENDCLASS(DamageText)
#endif
e.orb_radius = e.orb_radius/model_radius*0.6;
e.draw = orb_draw;
+ IL_PUSH(g_drawables, e);
e.health = 255;
e.move_movetype = MOVETYPE_NONE;
e.solid = SOLID_NOT;
#endif
#ifdef CSQC
-void Ent_WaypointSprite(entity this);
+void Ent_WaypointSprite(entity this, bool isnew);
NET_HANDLE(waypointsprites, bool isnew) {
- Ent_WaypointSprite(this);
+ Ent_WaypointSprite(this, isnew);
return true;
}
}
/** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
-void Ent_WaypointSprite(entity this)
+void Ent_WaypointSprite(entity this, bool isnew)
{
int sendflags = ReadByte();
this.wp_extra = ReadByte();
this.spawntime = time;
this.draw2d = Draw_WaypointSprite;
+ if (isnew) IL_PUSH(g_drawables_2d, this);
InterpolateOrigin_Undo(this);
this.iflags |= IFLAG_ORIGIN;
void Ent_RemoveWaypointSprite(entity this);
-void Ent_WaypointSprite(entity this);
+void Ent_WaypointSprite(entity this, bool isnew);
void WaypointSprite_Load_Frames(string ext);
if(autocvar_cl_simple_items && (this.ItemStatus & ITS_ALLOWSI))
{
+ if (isnew) IL_PUSH(g_drawables, this);
string _fn2 = substring(_fn, 0 , strlen(_fn) -4);
this.draw = ItemDrawSimple;
void conveyor_init(entity this)
{
this.draw = conveyor_draw;
+ IL_PUSH(g_drawables, this);
this.drawmask = MASK_NORMAL;
this.move_movetype = MOVETYPE_NONE;
this.move_movetype = MOVETYPE_PUSH;
this.drawmask = MASK_NORMAL;
this.draw = plat_draw;
+ if (isnew) IL_PUSH(g_drawables, this);
this.use = plat_use;
this.entremove = trigger_remove_generic;
setsize(this, this.mins, this.maxs);
this.solid = SOLID_NOT;
this.draw = Draw_PointParticles;
+ if (isnew) IL_PUSH(g_drawables, this);
this.entremove = Ent_PointParticles_Remove;
}
#endif
setorigin(this, this.origin);
setsize(this, this.mins, this.maxs);
this.solid = SOLID_NOT;
+ if (isnew) IL_PUSH(g_drawables, this);
if(this.impulse)
this.draw = Draw_Rain;
else
this.move_movetype = MOVETYPE_PUSH;
this.drawmask = MASK_NORMAL;
this.draw = train_draw;
+ if (isnew) IL_PUSH(g_drawables, this);
this.entremove = trigger_remove_generic;
if(set_platmovetype(this, this.platmovetype))
InterpolateOrigin_Note(this);
this.draw = Draw_Laser;
+ if (isnew) IL_PUSH(g_drawables, this);
}
#endif
this.tur_head.drawmask = MASK_NORMAL;
this.anim_start_time = 0;
this.draw2d = turret_draw2d;
+ IL_PUSH(g_drawables_2d, this);
this.maxdistance = autocvar_g_waypointsprite_turrets_maxdist;
this.teamradar_color = '1 0 0';
this.alpha = 1;
axh.colormod_z = ReadByte() / 255;
axh.cnt = time;
axh.draw2d = AuxiliaryXhair_Draw2D;
+ if (isnew) IL_PUSH(g_drawables_2d, axh);
return true;
}
this.lip = particleeffectnum(EFFECT_BUMBLEBEE_HEAL_IMPACT);
this.draw = bumble_raygun_draw;
+ if (isnew) IL_PUSH(g_drawables, this);
}
sfrag.solid = SOLID_CORPSE;
sfrag.draw = RaptorCBShellfragDraw;
+ IL_PUSH(g_drawables, sfrag);
sfrag.velocity = _vel;
sfrag.avelocity = prandomvec() * vlen(sfrag.velocity);
// set other main attributes of the beam
this.draw = Draw_ArcBeam;
+ IL_PUSH(g_drawables, this);
this.entremove = Remove_ArcBeam;
this.move_time = time;
loopsound(this, CH_SHOTS_SINGLE, SND(ARC_LOOP), VOL_BASE, ATTEN_NORM);
if(bIsNew || !this.teleport_time)
{
this.draw = Draw_GrapplingHook;
+ IL_PUSH(g_drawables, this);
this.entremove = Remove_GrapplingHook;
switch(this.HookType)
entity shockwave;
shockwave = spawn();
shockwave.draw = Draw_Shockwave;
+ IL_PUSH(g_drawables, shockwave);
shockwave.sw_shotorg_x = ReadCoord(); shockwave.sw_shotorg_y = ReadCoord(); shockwave.sw_shotorg_z = ReadCoord();
shockwave.sw_shotdir_x = ReadCoord(); shockwave.sw_shotdir_y = ReadCoord(); shockwave.sw_shotdir_z = ReadCoord();
setthink(this, SUB_Remove);
this.nextthink = time + bound(0, autocvar_cl_vaporizerbeam_lifetime, 10);
this.draw = VaporizerBeam_Draw;
+ if (isNew) IL_PUSH(g_drawables, this);
this.drawmask = MASK_NORMAL;
this.vorg1_x = ReadCoord(); this.vorg1_y = ReadCoord(); this.vorg1_z = ReadCoord();
int() _buf_create = #440;
#define buf_create _buf_create
+bool(entity ent) wasfreed = #353;
+
#pragma noref 0
#endif
#include "file.qh"
#include "functional.qh"
#include "i18n.qh"
+#include "intrusivelist.qh"
#include "iter.qh"
#include "json.qc"
#include "lazy.qh"
--- /dev/null
+#pragma once
+
+#include "iter.qh"
+
+const int IL_MAX = 128;
+
+void IL_INIT(entity this);
+void IL_DTOR(entity this);
+void IL_ENDFRAME();
+
+/**
+ * limitations:
+ * NULL cannot be present
+ * elements can only be present once
+ * a maximum of `IL_MAX` lists can exist at one time
+ * freed entities must be removed from the list
+ */
+CLASS(IntrusiveList, Object)
+ ATTRIB(IntrusiveList, il_head, entity, NULL);
+ ATTRIB(IntrusiveList, il_tail, entity, NULL);
+ ATTRIB(IntrusiveList, il_nextfld, .entity, nil);
+ ATTRIB(IntrusiveList, il_prevfld, .entity, nil);
+ INIT(IntrusiveList) { IL_INIT(this); }
+ DESTRUCTOR(IntrusiveList) { IL_DTOR(this); }
+ENDCLASS(IntrusiveList)
+
+// bitflags
+.vector il_lists;
+// bitflags
+.vector il_listmask;
+
+#define IL_NEW() NEW(IntrusiveList)
+
+#define IL_EMPTY(this) (this.il_head == NULL)
+
+#define IL_FIRST(this) (this.il_head)
+#define IL_LAST(this) (this.il_tail)
+#define IL_PEEK(this) (this.il_tail)
+
+bool IL_CONTAINS(IntrusiveList this, entity it)
+{
+ assert(this, return false);
+ return it.(this.il_nextfld) || this.il_head == it || this.il_tail == it;
+}
+
+/**
+ * Push to tail
+ */
+entity IL_PUSH(IntrusiveList this, entity it)
+{
+ assert(this, return NULL);
+ assert(it, return NULL);
+ .entity il_next = this.il_nextfld;
+ .entity il_prev = this.il_prevfld;
+ assert(!IL_CONTAINS(this, it), return NULL);
+
+ entity tail = it.(il_prev) = this.il_tail;
+ tail ? (tail.(il_next) = it) : this.il_head = it;
+ this.il_tail = it;
+ it.il_lists |= this.il_listmask;
+ return it;
+}
+
+/**
+ * Push to head
+ */
+entity IL_UNSHIFT(IntrusiveList this, entity it)
+{
+ assert(this, return NULL);
+ assert(it, return NULL);
+ .entity il_next = this.il_nextfld;
+ .entity il_prev = this.il_prevfld;
+ assert(!IL_CONTAINS(this, it), return NULL);
+
+ entity head = it.(il_next) = this.il_head;
+ head ? (head.(il_prev) = it) : this.il_tail = it;
+ this.il_head = it;
+ it.il_lists |= this.il_listmask;
+ return it;
+}
+
+/**
+ * Pop from tail
+ */
+entity IL_POP(IntrusiveList this)
+{
+ assert(this, return NULL);
+ .entity il_next = this.il_nextfld;
+ .entity il_prev = this.il_prevfld;
+
+ if (!this.il_tail) return NULL;
+ entity it = this.il_tail;
+ entity prev = it.(il_prev);
+ if (prev) (this.il_tail = prev).(il_next) = NULL;
+ else this.il_head = this.il_tail = NULL;
+ return it;
+}
+
+/**
+ * Pop from head
+ */
+entity IL_SHIFT(IntrusiveList this)
+{
+ assert(this, return NULL);
+ .entity il_next = this.il_nextfld;
+ .entity il_prev = this.il_prevfld;
+
+ if (!this.il_head) return NULL;
+ entity it = this.il_head;
+ entity next = it.(il_next);
+ if (next) (this.il_head = next).(il_prev) = NULL;
+ else this.il_head = this.il_tail = NULL;
+ return it;
+}
+
+/**
+ * Remove any element, anywhere in the list
+ */
+void IL_REMOVE(IntrusiveList this, entity it)
+{
+ assert(this, return);
+ .entity il_next = this.il_nextfld;
+ .entity il_prev = this.il_prevfld;
+ entity next = it.(il_next);
+ entity prev = it.(il_prev);
+ entity ohead = this.il_head;
+ entity otail = this.il_tail;
+ next ? next.(il_prev) = prev : this.il_tail = prev;
+ prev ? prev.(il_next) = next : this.il_head = next;
+ LOG_DEBUGF("remove %i (%i :: %i), head: %i -> %i, tail: %i -> %i\n", it, it.(il_prev), it.(il_next), ohead, this.il_head, otail, this.il_tail);
+ it.(il_next) = it.(il_prev) = NULL;
+}
+
+/**
+ * Remove all elements
+ */
+#define IL_CLEAR(this) \
+ MACRO_BEGIN \
+ { \
+ IntrusiveList __il = this; \
+ assert(__il); \
+ .entity il_prev = __il.il_prevfld; \
+ IL_EACH(__il, true, it.(il_next) = it.(il_prev) = NULL); \
+ __il.il_head = __il.il_tail = NULL; \
+ } MACRO_END
+
+/**
+ * Delete the list
+ */
+#define IL_DELETE(this, dtor) \
+ MACRO_BEGIN \
+ { \
+ remove(this); \
+ this = NULL; \
+ } MACRO_END
+
+#define IL_EACH(this, cond, body) \
+ MACRO_BEGIN \
+ { \
+ IntrusiveList _il = this; \
+ assert(_il); \
+ .entity il_next = _il.il_nextfld; \
+ noref int i = 0; \
+ for (entity _next, _it = _il.il_head; _it; (_it = _next, ++i)) \
+ { \
+ const noref entity it = _it; \
+ _next = it.(il_next); \
+ if (cond) { LAMBDA(body) } \
+ } \
+ } MACRO_END
+
+.int il_id;
+IntrusiveList il_links[IL_MAX];
+.entity il_links_flds[IL_MAX * 2];
+int il_links_ptr;
+
+#define IL_FLOOR(n) ((n) | 0)
+#define IL_CEIL(n) IL_FLOOR((n) + 0.5)
+
+#define IL_LISTS_PER_BIT IL_CEIL(IL_MAX / (3 * 24))
+
+void IL_INIT(IntrusiveList this)
+{
+ .entity nextfld, prevfld;
+ for (int i = il_links_ptr; i < il_links_ptr + IL_MAX; ++i) {
+ int idx = i;
+ if (idx >= IL_MAX) idx -= IL_MAX;
+ int id = idx;
+ idx *= 2;
+ if (!il_links[idx]) {
+ il_links[idx] = this;
+ nextfld = il_links_flds[idx + 0];
+ prevfld = il_links_flds[idx + 1];
+ this.il_id = id;
+ int bit = IL_FLOOR(id / IL_LISTS_PER_BIT);
+ if (bit < (1 * 24)) this.il_listmask = '1 0 0' * (1 << (bit - (0 * 24)));
+ else if (bit < (2 * 24)) this.il_listmask = '0 1 0' * (1 << (bit - (1 * 24)));
+ else if (bit < (3 * 24)) this.il_listmask = '0 0 1' * (1 << (bit - (2 * 24)));
+ else assert(false);
+ il_links_ptr = id + 1;
+ if (il_links_ptr >= IL_MAX) il_links_ptr -= IL_MAX;
+ this.il_nextfld = nextfld;
+ this.il_prevfld = prevfld;
+ return;
+ }
+ }
+ LOG_WARNINGF("IntrusiveList overflow");
+}
+
+void IL_DTOR(IntrusiveList this)
+{
+ IL_CLEAR(this);
+ il_links[this.il_id] = NULL;
+}
+
+void IL_ENDFRAME()
+{
+#if 0
+ // incompatible with CSQC, remove() clears entities
+ for (int i = 0; i < IL_MAX; ++i) {
+ IntrusiveList list = il_links[i];
+ if (list) {
+ .entity nextfld = list.il_nextfld;
+ for (entity next, it = list.il_head; it; it = next) {
+ next = it.(nextfld);
+ if (wasfreed(it)) {
+ IL_REMOVE(list, it);
+ }
+ }
+ }
+ }
+#endif
+}
+
+void ONREMOVE(entity this)
+{
+ if (this.il_lists) {
+ for (int i = 0; i < IL_MAX; ++i) {
+ IntrusiveList list = il_links[i];
+ if (this.il_lists & list.il_listmask && IL_CONTAINS(list, this)) {
+ IL_REMOVE(list, this);
+ }
+ }
+ }
+}
#define new_pure(class) _new(class, true)
#define spawn() __spawn("entity", __FILE__ ":" STR(__LINE__), false)
+[[accumulate]] void ONREMOVE(entity this) {}
+
#define delete(this) MACRO_BEGIN { \
entity _this = (this); \
void(entity) _dtor = _this.dtor; \
+ ONREMOVE(this); \
if (_dtor) _dtor(_this); else remove(_this); \
/* this = NULL; */ \
} MACRO_END
postMenuDraw();
frametime = 0;
+ IL_ENDFRAME();
}
void m_display()
PlayerState s = PS(it);
s.ps_push(s, it);
});
+ IL_ENDFRAME();
}