From 86770ea89a714d75a001c042eee8e349c1e80750 Mon Sep 17 00:00:00 2001 From: legokidlogan <26103433+legokidlogan@users.noreply.github.com> Date: Sun, 6 Oct 2024 10:42:47 -0600 Subject: [PATCH 01/12] Allow for multiple collision listeners --- lua/starfall/libs_sv/entities.lua | 215 +++++++++++++++++++++++++----- 1 file changed, 179 insertions(+), 36 deletions(-) diff --git a/lua/starfall/libs_sv/entities.lua b/lua/starfall/libs_sv/entities.lua index eeedf655f..9ea6aa58c 100644 --- a/lua/starfall/libs_sv/entities.lua +++ b/lua/starfall/libs_sv/entities.lua @@ -9,6 +9,10 @@ local isentity = isentity local huge = math.huge local abs = math.abs +local collisionListenerLimit = SF.LimitObject("collisionlistener", "collisionlistner", 200, "The number of concurrent starfall collision listeners") +local collisionListenerInstanceInfosPerEnt = {} -- { [ent] = { [instance] = { listeners = { [name/num] = function } } } } +local collisionQueue = {} + -- Register privileges registerprivilege("entities.applyDamage", "Apply damage", "Allows the user to apply damage to an entity", { entities = {} }) registerprivilege("entities.applyForce", "Apply force", "Allows the user to apply force to an entity", { entities = {} }) @@ -39,6 +43,41 @@ local function checkvector(v) end end +local function addCollisions(ent) + return function(data) + if next(collisionQueue) == nil then + timer.Simple(0, function() -- TODO: Is this timer necessary? It greatly overcomplicates the callback system. If so, the reason should be noted here. + for i = 1, #collisionQueue do + local collision = collisionQueue[i] + local thisEnt = collision.ent + + collisionQueue[i] = nil + + if not IsValid(thisEnt) then continue end + + local thisData = collision.data + local infosPerInstance = collisionListenerInstanceInfosPerEnt[thisEnt] + if not infosPerInstance then continue end -- Could be nil if listeners were removed during the timer.Simple + + for instance, listenerInfo in pairs(infosPerInstance) do + local thisDataWrapped = SF.StructWrapper(instance, thisData, "CollisionData") + local listeners = listenerInfo.listeners + + for _, func in pairs(listeners) do + instance:runFunction(func, thisDataWrapped) + end + end + end + end) + end + + collisionQueue[#collisionQueue + 1] = { + ent = ent, + data = data, + } + end +end + return function(instance) local base_physicscollide = baseclass.Get("base_gmodentity").PhysicsCollide @@ -51,20 +90,45 @@ local vec_meta, vwrap, vunwrap = instance.Types.Vector, instance.Types.Vector.Wr local cunwrap = instance.Types.Color.Unwrap local getent -local collisionlisteners = {} +local entsWithCollisionListeners = {} -- Lookup for which ents this instance made collision listeners for instance:AddHook("initialize", function() getent = ent_meta.GetEntity end) instance:AddHook("deinitialize", function() - for ent in pairs(collisionlisteners) do - if IsValid(ent) then - if ent:IsScripted() then - ent.PhysicsCollide = base_physicscollide - else - ent:RemoveCallback("PhysicsCollide", ent.SF_CollisionCallback) - ent.SF_CollisionCallback = nil + for ent in pairs(entsWithCollisionListeners) do + local infosPerInstance = collisionListenerInstanceInfosPerEnt[ent] + if not infosPerInstance then continue end -- May be nil if the entity was removed before deinitialization (due to the SF.CallOnRemove()) + + local listenerInfo = infosPerInstance[instance] + if listenerInfo then continue end -- Shouldn't ever be nil, but for in case + + local listeners = listenerInfo.listeners + + for name in pairs(listeners) do + collisionListenerLimit:free(instance.player, 1) + listeners[name] = nil -- Faster GC + end + + infosPerInstance[instance] = nil + + -- Ent has no more listeners across all instances, remove the callbacks/wraps + if next(infosPerInstance) == nil then + if IsValid(ent) then + local oldPhysicsCollide = ent.SF_OldPhysicsCollide -- non-nil if the entity is scripted + + if oldPhysicsCollide then + ent.PhysicsCollide = oldPhysicsCollide + else + ent:RemoveCallback("PhysicsCollide", ent.SF_CollisionCallback) + end + + SF.RemoveCallOnRemove(ent, "RemoveCollisionListeners") end + + collisionListenerInstanceInfosPerEnt[ent] = nil end + + entsWithCollisionListeners[ent] = nil -- Faster GC end end) @@ -363,53 +427,132 @@ function ents_methods:applyTorque(torque) phys:ApplyTorqueCenter(torque) end -local entity_collisions = {} -local function addCollisions(func) - return function(data) - if next(entity_collisions)==nil then - timer.Simple(0, function() - for i=1, #entity_collisions do - instance:runFunction(func, SF.StructWrapper(instance, entity_collisions[i], "CollisionData")) - entity_collisions[i] = nil - end - end) - end - entity_collisions[#entity_collisions+1] = data - end -end ---- Allows detecting collisions on an entity. You can only do this once for the entity's entire lifespan so use it wisely. +--- Allows detecting collisions on an entity. -- @param function func The callback function with argument, table collsiondata, http://wiki.facepunch.com/gmod/Structures/CollisionData -function ents_methods:addCollisionListener(func) +-- @param string? name Optional name to distinguish multiple collision listeners and remove them individually later. +function ents_methods:addCollisionListener(func, name) + collisionListenerLimit:checkuse(instance.player, 1) + local ent = getent(self) - if collisionlisteners[ent] then SF.Throw("The entity is already listening to collisions!", 2) end checkluatype(func, TYPE_FUNCTION) checkpermission(instance, ent, "entities.canTool") - local callback = addCollisions(func) + local infosPerInstance = collisionListenerInstanceInfosPerEnt[ent] + local alreadyHasListeners = infosPerInstance ~= nil + + if not infosPerInstance then + infosPerInstance = {} + collisionListenerInstanceInfosPerEnt[ent] = infosPerInstance + end + + local listenerInfo = infosPerInstance[instance] + local listeners + + if listenerInfo then + listeners = listenerInfo.listeners + else + listeners = {} + listenerInfo = {listeners = listeners} + infosPerInstance[instance] = listenerInfo + end + + if name ~= nil then + checkluatype(name, TYPE_STRING) + + if listeners[name] then SF.Throw("The entity already has a collision listener with that name", 2) end + end + + if name ~= nil then + listeners[name] = func + else + listeners[#listeners + 1] = func + end + + collisionListenerLimit:free(instance.player, -1) + entsWithCollisionListeners[ent] = true + + if alreadyHasListeners then return end + + local callback = addCollisions(ent) + if ent:IsScripted() then - if ent.PhysicsCollide ~= base_physicscollide and ent.PhysicsCollide ~= nil then SF.Throw("The entity is already listening to collisions!", 2) end - function ent:PhysicsCollide( data, phys ) callback(data) end + local oldPhysicsCollide = ent.PhysicsCollide or base_physicscollide + ent.SF_OldPhysicsCollide = oldPhysicsCollide + + function ent:PhysicsCollide(data, phys) + oldPhysicsCollide(self, data, phys) + callback(data) + end else ent.SF_CollisionCallback = ent:AddCallback("PhysicsCollide", function(ent, data) callback(data) end) end - collisionlisteners[ent] = true + + SF.CallOnRemove(ent, "RemoveCollisionListeners", function() + local theseInfosPerInstance = collisionListenerInstanceInfosPerEnt[ent] + if not theseInfosPerInstance then return end + + for thisInstance, thisListenerInfo in pairs(theseInfosPerInstance) do + local theseListeners = thisListenerInfo.listeners + + for listenerName in pairs(theseListeners) do + collisionListenerLimit:free(thisInstance.player, 1) + theseListeners[listenerName] = nil -- Faster GC + end + + theseInfosPerInstance[thisInstance] = nil -- Faster GC + end + + collisionListenerInstanceInfosPerEnt[ent] = nil -- Signify that listeners have been removed + end) end ---- Removes a collision listening hook from the entity so that a new one can be added -function ents_methods:removeCollisionListener() +--- Removes a collision listener from the entity +-- @param string? name The name of the collision listener to remove. If nil, will remove all listeners. +function ents_methods:removeCollisionListener(name) local ent = getent(self) - if not collisionlisteners[ent] then SF.Throw("The entity isn't listening to collisions!", 2) end + if name ~= nil then checkluatype(name, TYPE_STRING) end + + local infosPerInstance = collisionListenerInstanceInfosPerEnt[ent] + if not infosPerInstance then return end + + local listenerInfo = infosPerInstance[instance] + if not listenerInfo then return end checkpermission(instance, ent, "entities.canTool") - if ent:IsScripted() then - ent.PhysicsCollide = base_physicscollide + local listeners = listenerInfo.listeners + + if name ~= nil then + if listeners[name] then + collisionListenerLimit:free(instance.player, 1) + listeners[name] = nil + end + else + for listenerName in pairs(listeners) do + collisionListenerLimit:free(instance.player, 1) + listeners[listenerName] = nil + end + end + + if next(listeners) ~= nil then return end + + infosPerInstance[instance] = nil + entsWithCollisionListeners[ent] = nil + + if next(infosPerInstance) ~= nil then return end + + local oldPhysicsCollide = ent.SF_OldPhysicsCollide + + if oldPhysicsCollide then + ent.PhysicsCollide = oldPhysicsCollide else ent:RemoveCallback("PhysicsCollide", ent.SF_CollisionCallback) - ent.SF_CollisionCallback = nil end - collisionlisteners[ent] = nil + + SF.RemoveCallOnRemove(ent, "RemoveCollisionListeners") + + collisionListenerInstanceInfosPerEnt[ent] = nil end --- Sets whether an entity's shadow should be drawn From b52875e48d33b59cee1308064b66095ca50997eb Mon Sep 17 00:00:00 2001 From: legokidlogan <26103433+legokidlogan@users.noreply.github.com> Date: Sun, 6 Oct 2024 10:48:03 -0600 Subject: [PATCH 02/12] Add :hasCollisionListener() --- lua/starfall/libs_sv/entities.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lua/starfall/libs_sv/entities.lua b/lua/starfall/libs_sv/entities.lua index 9ea6aa58c..492841266 100644 --- a/lua/starfall/libs_sv/entities.lua +++ b/lua/starfall/libs_sv/entities.lua @@ -555,6 +555,23 @@ function ents_methods:removeCollisionListener(name) collisionListenerInstanceInfosPerEnt[ent] = nil end +--- Checks if an entity has a collision listener +--- Only checks for listeners created by this chip +-- @param string? name The name of the collision listener to check for. If nil, will check if any listeners exist. +function ents_methods:hasCollisionListener( name ) + local ent = getent(self) + if name ~= nil then checkluatype(name, TYPE_STRING) end + + local infosPerInstance = collisionListenerInstanceInfosPerEnt[ent] + if not infosPerInstance then return false end + + local listenerInfo = infosPerInstance[instance] + if not listenerInfo then return false end + if name == nil then return true end -- When listenerInfo ~= nil, there's at least one listener + + return listenerInfo.listeners[name] ~= nil +end + --- Sets whether an entity's shadow should be drawn -- @param boolean draw Whether the shadow should draw function ents_methods:setDrawShadow(draw) From 17485a594208a542640774a60bde22b54028e634 Mon Sep 17 00:00:00 2001 From: legokidlogan <26103433+legokidlogan@users.noreply.github.com> Date: Sun, 6 Oct 2024 10:50:20 -0600 Subject: [PATCH 03/12] Add :getCollisionListenerNames() --- lua/starfall/libs_sv/entities.lua | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lua/starfall/libs_sv/entities.lua b/lua/starfall/libs_sv/entities.lua index 492841266..db04640f2 100644 --- a/lua/starfall/libs_sv/entities.lua +++ b/lua/starfall/libs_sv/entities.lua @@ -572,6 +572,30 @@ function ents_methods:hasCollisionListener( name ) return listenerInfo.listeners[name] ~= nil end +--- Returns a table of all collision listener names +--- Only returns listeners created by this chip +--- Nameless listeners will not be included +function ents_methods:getCollisionListenerNames() + local ent = getent(self) + + local infosPerInstance = collisionListenerInstanceInfosPerEnt[ent] + if not infosPerInstance then return {} end + + local listenerInfo = infosPerInstance[instance] + if not listenerInfo then return {} end + + local listeners = listenerInfo.listeners + local names = {} + + for name in pairs(listeners) do + if type(name) == "string" then + names[#names + 1] = name + end + end + + return names +end + --- Sets whether an entity's shadow should be drawn -- @param boolean draw Whether the shadow should draw function ents_methods:setDrawShadow(draw) From 7df5b97e574717e82c475f4af96a69d1848e7af5 Mon Sep 17 00:00:00 2001 From: legokidlogan <26103433+legokidlogan@users.noreply.github.com> Date: Sun, 6 Oct 2024 11:20:26 -0600 Subject: [PATCH 04/12] Remove structural relic --- lua/starfall/libs_sv/entities.lua | 94 ++++++++++++++----------------- 1 file changed, 41 insertions(+), 53 deletions(-) diff --git a/lua/starfall/libs_sv/entities.lua b/lua/starfall/libs_sv/entities.lua index db04640f2..b2969829d 100644 --- a/lua/starfall/libs_sv/entities.lua +++ b/lua/starfall/libs_sv/entities.lua @@ -10,7 +10,7 @@ local huge = math.huge local abs = math.abs local collisionListenerLimit = SF.LimitObject("collisionlistener", "collisionlistner", 200, "The number of concurrent starfall collision listeners") -local collisionListenerInstanceInfosPerEnt = {} -- { [ent] = { [instance] = { listeners = { [name/num] = function } } } } +local instanceCollisionListenersPerEnt = {} -- { [ent] = { [instance] = { [name/num] = function } } } local collisionQueue = {} -- Register privileges @@ -56,12 +56,11 @@ local function addCollisions(ent) if not IsValid(thisEnt) then continue end local thisData = collision.data - local infosPerInstance = collisionListenerInstanceInfosPerEnt[thisEnt] - if not infosPerInstance then continue end -- Could be nil if listeners were removed during the timer.Simple + local listenersPerInstance = instanceCollisionListenersPerEnt[thisEnt] + if not listenersPerInstance then continue end -- Could be nil if listeners were removed during the timer.Simple - for instance, listenerInfo in pairs(infosPerInstance) do + for instance, listeners in pairs(listenersPerInstance) do local thisDataWrapped = SF.StructWrapper(instance, thisData, "CollisionData") - local listeners = listenerInfo.listeners for _, func in pairs(listeners) do instance:runFunction(func, thisDataWrapped) @@ -96,23 +95,21 @@ instance:AddHook("initialize", function() end) instance:AddHook("deinitialize", function() for ent in pairs(entsWithCollisionListeners) do - local infosPerInstance = collisionListenerInstanceInfosPerEnt[ent] - if not infosPerInstance then continue end -- May be nil if the entity was removed before deinitialization (due to the SF.CallOnRemove()) + local listenersPerInstance = instanceCollisionListenersPerEnt[ent] + if not listenersPerInstance then continue end -- May be nil if the entity was removed before deinitialization (due to the SF.CallOnRemove()) - local listenerInfo = infosPerInstance[instance] - if listenerInfo then continue end -- Shouldn't ever be nil, but for in case - - local listeners = listenerInfo.listeners + local listeners = listenersPerInstance[instance] + if listeners then continue end -- Shouldn't ever be nil, but for in case for name in pairs(listeners) do collisionListenerLimit:free(instance.player, 1) listeners[name] = nil -- Faster GC end - infosPerInstance[instance] = nil + listenersPerInstance[instance] = nil -- Ent has no more listeners across all instances, remove the callbacks/wraps - if next(infosPerInstance) == nil then + if next(listenersPerInstance) == nil then if IsValid(ent) then local oldPhysicsCollide = ent.SF_OldPhysicsCollide -- non-nil if the entity is scripted @@ -125,7 +122,7 @@ instance:AddHook("deinitialize", function() SF.RemoveCallOnRemove(ent, "RemoveCollisionListeners") end - collisionListenerInstanceInfosPerEnt[ent] = nil + instanceCollisionListenersPerEnt[ent] = nil end entsWithCollisionListeners[ent] = nil -- Faster GC @@ -438,23 +435,19 @@ function ents_methods:addCollisionListener(func, name) checkluatype(func, TYPE_FUNCTION) checkpermission(instance, ent, "entities.canTool") - local infosPerInstance = collisionListenerInstanceInfosPerEnt[ent] - local alreadyHasListeners = infosPerInstance ~= nil + local listenersPerInstance = instanceCollisionListenersPerEnt[ent] + local alreadyHasListeners = listenersPerInstance ~= nil - if not infosPerInstance then - infosPerInstance = {} - collisionListenerInstanceInfosPerEnt[ent] = infosPerInstance + if not listenersPerInstance then + listenersPerInstance = {} + instanceCollisionListenersPerEnt[ent] = listenersPerInstance end - local listenerInfo = infosPerInstance[instance] - local listeners + local listeners = listenersPerInstance[instance] - if listenerInfo then - listeners = listenerInfo.listeners - else + if not listeners then listeners = {} - listenerInfo = {listeners = listeners} - infosPerInstance[instance] = listenerInfo + listenersPerInstance[instance] = listeners end if name ~= nil then @@ -489,21 +482,19 @@ function ents_methods:addCollisionListener(func, name) end SF.CallOnRemove(ent, "RemoveCollisionListeners", function() - local theseInfosPerInstance = collisionListenerInstanceInfosPerEnt[ent] - if not theseInfosPerInstance then return end - - for thisInstance, thisListenerInfo in pairs(theseInfosPerInstance) do - local theseListeners = thisListenerInfo.listeners + local theseListenersPerInstance = instanceCollisionListenersPerEnt[ent] + if not theseListenersPerInstance then return end + for thisInstance, theseListeners in pairs(theseListenersPerInstance) do for listenerName in pairs(theseListeners) do collisionListenerLimit:free(thisInstance.player, 1) theseListeners[listenerName] = nil -- Faster GC end - theseInfosPerInstance[thisInstance] = nil -- Faster GC + theseListenersPerInstance[thisInstance] = nil -- Faster GC end - collisionListenerInstanceInfosPerEnt[ent] = nil -- Signify that listeners have been removed + instanceCollisionListenersPerEnt[ent] = nil -- Signify that listeners have been removed end) end @@ -513,16 +504,14 @@ function ents_methods:removeCollisionListener(name) local ent = getent(self) if name ~= nil then checkluatype(name, TYPE_STRING) end - local infosPerInstance = collisionListenerInstanceInfosPerEnt[ent] - if not infosPerInstance then return end + local listenersPerInstance = instanceCollisionListenersPerEnt[ent] + if not listenersPerInstance then return end - local listenerInfo = infosPerInstance[instance] - if not listenerInfo then return end + local listeners = listenersPerInstance[instance] + if not listeners then return end checkpermission(instance, ent, "entities.canTool") - local listeners = listenerInfo.listeners - if name ~= nil then if listeners[name] then collisionListenerLimit:free(instance.player, 1) @@ -537,10 +526,10 @@ function ents_methods:removeCollisionListener(name) if next(listeners) ~= nil then return end - infosPerInstance[instance] = nil + listenersPerInstance[instance] = nil entsWithCollisionListeners[ent] = nil - if next(infosPerInstance) ~= nil then return end + if next(listenersPerInstance) ~= nil then return end local oldPhysicsCollide = ent.SF_OldPhysicsCollide @@ -552,7 +541,7 @@ function ents_methods:removeCollisionListener(name) SF.RemoveCallOnRemove(ent, "RemoveCollisionListeners") - collisionListenerInstanceInfosPerEnt[ent] = nil + instanceCollisionListenersPerEnt[ent] = nil end --- Checks if an entity has a collision listener @@ -562,14 +551,14 @@ function ents_methods:hasCollisionListener( name ) local ent = getent(self) if name ~= nil then checkluatype(name, TYPE_STRING) end - local infosPerInstance = collisionListenerInstanceInfosPerEnt[ent] - if not infosPerInstance then return false end + local listenersPerInstance = instanceCollisionListenersPerEnt[ent] + if not listenersPerInstance then return false end - local listenerInfo = infosPerInstance[instance] - if not listenerInfo then return false end - if name == nil then return true end -- When listenerInfo ~= nil, there's at least one listener + local listeners = listenersPerInstance[instance] + if not listeners then return false end + if name == nil then return true end -- When listeners ~= nil, there's at least one listener - return listenerInfo.listeners[name] ~= nil + return listeners[name] ~= nil end --- Returns a table of all collision listener names @@ -578,13 +567,12 @@ end function ents_methods:getCollisionListenerNames() local ent = getent(self) - local infosPerInstance = collisionListenerInstanceInfosPerEnt[ent] - if not infosPerInstance then return {} end + local listenersPerInstance = instanceCollisionListenersPerEnt[ent] + if not listenersPerInstance then return {} end - local listenerInfo = infosPerInstance[instance] - if not listenerInfo then return {} end + local listeners = listenersPerInstance[instance] + if not listeners then return {} end - local listeners = listenerInfo.listeners local names = {} for name in pairs(listeners) do From bea7a23b9cbf476cc3974077c91716f4ce34dcda Mon Sep 17 00:00:00 2001 From: legokidlogan <26103433+legokidlogan@users.noreply.github.com> Date: Sun, 6 Oct 2024 11:20:42 -0600 Subject: [PATCH 05/12] Clarity --- lua/starfall/libs_sv/entities.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/starfall/libs_sv/entities.lua b/lua/starfall/libs_sv/entities.lua index b2969829d..37a0b61aa 100644 --- a/lua/starfall/libs_sv/entities.lua +++ b/lua/starfall/libs_sv/entities.lua @@ -99,7 +99,7 @@ instance:AddHook("deinitialize", function() if not listenersPerInstance then continue end -- May be nil if the entity was removed before deinitialization (due to the SF.CallOnRemove()) local listeners = listenersPerInstance[instance] - if listeners then continue end -- Shouldn't ever be nil, but for in case + if listeners then continue end -- Shouldn't be nil at this point, but for in case for name in pairs(listeners) do collisionListenerLimit:free(instance.player, 1) From 34ef5fd64fde9364858fcee4cd92a3832008596d Mon Sep 17 00:00:00 2001 From: thegrb93 Date: Tue, 8 Oct 2024 01:51:22 -0400 Subject: [PATCH 06/12] cleanup and optimize --- lua/starfall/libs_sv/entities.lua | 381 +++++++++++++----------------- 1 file changed, 163 insertions(+), 218 deletions(-) diff --git a/lua/starfall/libs_sv/entities.lua b/lua/starfall/libs_sv/entities.lua index 37a0b61aa..c49847337 100644 --- a/lua/starfall/libs_sv/entities.lua +++ b/lua/starfall/libs_sv/entities.lua @@ -9,10 +9,6 @@ local isentity = isentity local huge = math.huge local abs = math.abs -local collisionListenerLimit = SF.LimitObject("collisionlistener", "collisionlistner", 200, "The number of concurrent starfall collision listeners") -local instanceCollisionListenersPerEnt = {} -- { [ent] = { [instance] = { [name/num] = function } } } -local collisionQueue = {} - -- Register privileges registerprivilege("entities.applyDamage", "Apply damage", "Allows the user to apply damage to an entity", { entities = {} }) registerprivilege("entities.applyForce", "Apply force", "Allows the user to apply force to an entity", { entities = {} }) @@ -34,6 +30,156 @@ registerprivilege("entities.canTool", "CanTool", "Whether or not the user can us registerprivilege("entities.use", "Use", "Whether or not the user can use the entity", { entities = {} }) registerprivilege("entities.getTable", "GetTable", "Allows the user to get an entity's table", { entities = {}, usergroups = { default = 1 } }) + +local collisionListenerLimit = SF.LimitObject("collisionlistener", "collisionlistner", 200, "The number of concurrent starfall collision listeners") +local base_physicscollide +SF.GlobalCollisionListener = { + __index = { + create = function(self, ent) + local listenertable = {} + + local queue = {} + local function collisionQueueProcess() + if IsValid(ent) then + for _, listener in ipairs(listenertable) do + local instance = listener.instance + for i=1, #queue do + listener:run(SF.StructWrapper(instance, queue[i], "CollisionData")) + end + end + end + for i=1, #queue do + queue[i] = nil + end + end + + local function collisionQueueCallback(ent, data) + local i = #queue+1 + queue[i] = data + if i==1 then timer.Simple(0, collisionQueueProcess) end + end + + if ent:IsScripted() then + local oldPhysicsCollide = ent.PhysicsCollide or base_physicscollide + ent.SF_OldPhysicsCollide = oldPhysicsCollide + + function ent:PhysicsCollide(data, phys) + oldPhysicsCollide(self, data, phys) + collisionQueueCallback(self, data) + end + else + ent.SF_CollisionCallback = ent:AddCallback("PhysicsCollide", collisionQueueCallback) + end + SF.CallOnRemove(ent, "RemoveCollisionListeners", function(e) self:destroy(e) end) + + self.listeners[ent] = listenertable + return listenertable + end, + destroy = function(self, ent) + local entlisteners = self.listeners[ent] + if entlisteners==nil then return end + self.listeners[ent] = nil + for _, listener in ipairs(entlisteners) do + listener.manager:free(ent, listener) + end + if IsValid(ent) then + local oldPhysicsCollide = ent.SF_OldPhysicsCollide + if oldPhysicsCollide then + ent.PhysicsCollide = oldPhysicsCollide + else + ent:RemoveCallback("PhysicsCollide", ent.SF_CollisionCallback) + end + + SF.RemoveCallOnRemove(ent, "RemoveCollisionListeners") + end + end, + add = function(self, ent, listener) + local entlisteners = self.listeners[ent] + if entlisteners == nil then + entlisteners = self:create(ent) + elseif table.HasValue(entlisteners, listener) then + return + end + entlisteners[#entlisteners + 1] = listener + end, + remove = function(self, ent, listener) + local entlisteners = self.listeners[ent] + if entlisteners==nil then return end + local i = table.HasValue(entlisteners, listener) + if i==nil then return end + + entlisteners[i] = entlisteners[#entlisteners] + entlisteners[#entlisteners] = nil + if entlisteners[1]==nil then self:destroy(ent) end + end + }, + __call = function(p) + return setmetatable({ + listeners = {} + }, p) + end +} +setmetatable(SF.GlobalCollisionListener, SF.GlobalCollisionListener) +local globalListeners = SF.GlobalCollisionListener() + +SF.InstanceCollisionListener = { + __index = { + add = function(self, ent, name, func) + local created = false + local listener = self.hooksPerEnt[ent] + if listener==nil then + collisionListenerLimit:checkuse(self.instance.player, 1) + listener = SF.HookTable() + listener.manager = self + listener.instance = self.instance + self.hooksPerEnt[ent] = listener + + globalListeners:add(listener) + created = true + elseif not listener:exists(name) then + collisionListenerLimit:checkuse(self.instance.player, 1) + created = true + end + + listener:add(name, func) + + if created then + collisionListenerLimit:free(self.instance.player, -1) + end + end, + remove = function(self, ent, name) + local listener = self.hooksPerEnt[ent] + if listener and listener:exists(name) then + listener:remove(name) + collisionListenerLimit:free(self.instance.player, 1) + if listener:isEmpty() then + globalListeners:remove(listener) + self.hooksPerEnt[ent] = nil + end + end + end, + free = function(self, ent, listener) + collisionListenerLimit:free(self.instance.player, listener.n) + self.hooksPerEnt[ent] = nil + end, + destroy = function(self) + for ent, listener in pairs(self.hooksPerEnt) do + collisionListenerLimit:free(self.instance.player, listener.n) + self.hooksPerEnt[ent] = nil + + globalListeners:remove(listener) + end + end + }, + __call = function(p, instance) + return setmetatable({ + instance = instance, + hooksPerEnt = {} + }, p) + end +} +setmetatable(SF.InstanceCollisionListener, SF.InstanceCollisionListener) + local function checkvector(v) if v[1]<-1e12 or v[1]>1e12 or v[1]~=v[1] or v[2]<-1e12 or v[2]>1e12 or v[2]~=v[2] or @@ -43,43 +189,7 @@ local function checkvector(v) end end -local function addCollisions(ent) - return function(data) - if next(collisionQueue) == nil then - timer.Simple(0, function() -- TODO: Is this timer necessary? It greatly overcomplicates the callback system. If so, the reason should be noted here. - for i = 1, #collisionQueue do - local collision = collisionQueue[i] - local thisEnt = collision.ent - - collisionQueue[i] = nil - - if not IsValid(thisEnt) then continue end - - local thisData = collision.data - local listenersPerInstance = instanceCollisionListenersPerEnt[thisEnt] - if not listenersPerInstance then continue end -- Could be nil if listeners were removed during the timer.Simple - - for instance, listeners in pairs(listenersPerInstance) do - local thisDataWrapped = SF.StructWrapper(instance, thisData, "CollisionData") - - for _, func in pairs(listeners) do - instance:runFunction(func, thisDataWrapped) - end - end - end - end) - end - - collisionQueue[#collisionQueue + 1] = { - ent = ent, - data = data, - } - end -end - - return function(instance) -local base_physicscollide = baseclass.Get("base_gmodentity").PhysicsCollide local checkpermission = instance.player ~= SF.Superuser and SF.Permissions.check or function() end local owrap, ounwrap = instance.WrapObject, instance.UnwrapObject @@ -88,45 +198,16 @@ local ang_meta, awrap, aunwrap = instance.Types.Angle, instance.Types.Angle.Wrap local vec_meta, vwrap, vunwrap = instance.Types.Vector, instance.Types.Vector.Wrap, instance.Types.Vector.Unwrap local cunwrap = instance.Types.Color.Unwrap +local collisionListeners = SF.InstanceCollisionListener(instance) +base_physicscollide = baseclass.Get("base_gmodentity").PhysicsCollide + local getent -local entsWithCollisionListeners = {} -- Lookup for which ents this instance made collision listeners for instance:AddHook("initialize", function() getent = ent_meta.GetEntity end) -instance:AddHook("deinitialize", function() - for ent in pairs(entsWithCollisionListeners) do - local listenersPerInstance = instanceCollisionListenersPerEnt[ent] - if not listenersPerInstance then continue end -- May be nil if the entity was removed before deinitialization (due to the SF.CallOnRemove()) - - local listeners = listenersPerInstance[instance] - if listeners then continue end -- Shouldn't be nil at this point, but for in case - - for name in pairs(listeners) do - collisionListenerLimit:free(instance.player, 1) - listeners[name] = nil -- Faster GC - end - listenersPerInstance[instance] = nil - - -- Ent has no more listeners across all instances, remove the callbacks/wraps - if next(listenersPerInstance) == nil then - if IsValid(ent) then - local oldPhysicsCollide = ent.SF_OldPhysicsCollide -- non-nil if the entity is scripted - - if oldPhysicsCollide then - ent.PhysicsCollide = oldPhysicsCollide - else - ent:RemoveCallback("PhysicsCollide", ent.SF_CollisionCallback) - end - - SF.RemoveCallOnRemove(ent, "RemoveCollisionListeners") - end - - instanceCollisionListenersPerEnt[ent] = nil - end - - entsWithCollisionListeners[ent] = nil -- Faster GC - end +instance:AddHook("deinitialize", function() + collisionListeners:destroy() end) -- ------------------------- Methods ------------------------- -- @@ -426,162 +507,26 @@ end --- Allows detecting collisions on an entity. -- @param function func The callback function with argument, table collsiondata, http://wiki.facepunch.com/gmod/Structures/CollisionData --- @param string? name Optional name to distinguish multiple collision listeners and remove them individually later. +-- @param string? name Optional name to distinguish multiple collision listeners and remove them individually later. (default: "") function ents_methods:addCollisionListener(func, name) - collisionListenerLimit:checkuse(instance.player, 1) + checkluatype(func, TYPE_FUNCTION) + if name ~= nil then checkluatype(name, TYPE_STRING) else name = "" end local ent = getent(self) - - checkluatype(func, TYPE_FUNCTION) checkpermission(instance, ent, "entities.canTool") - local listenersPerInstance = instanceCollisionListenersPerEnt[ent] - local alreadyHasListeners = listenersPerInstance ~= nil - - if not listenersPerInstance then - listenersPerInstance = {} - instanceCollisionListenersPerEnt[ent] = listenersPerInstance - end - - local listeners = listenersPerInstance[instance] - - if not listeners then - listeners = {} - listenersPerInstance[instance] = listeners - end - - if name ~= nil then - checkluatype(name, TYPE_STRING) - - if listeners[name] then SF.Throw("The entity already has a collision listener with that name", 2) end - end - - if name ~= nil then - listeners[name] = func - else - listeners[#listeners + 1] = func - end - - collisionListenerLimit:free(instance.player, -1) - entsWithCollisionListeners[ent] = true - - if alreadyHasListeners then return end - - local callback = addCollisions(ent) - - if ent:IsScripted() then - local oldPhysicsCollide = ent.PhysicsCollide or base_physicscollide - ent.SF_OldPhysicsCollide = oldPhysicsCollide - - function ent:PhysicsCollide(data, phys) - oldPhysicsCollide(self, data, phys) - callback(data) - end - else - ent.SF_CollisionCallback = ent:AddCallback("PhysicsCollide", function(ent, data) callback(data) end) - end - - SF.CallOnRemove(ent, "RemoveCollisionListeners", function() - local theseListenersPerInstance = instanceCollisionListenersPerEnt[ent] - if not theseListenersPerInstance then return end - - for thisInstance, theseListeners in pairs(theseListenersPerInstance) do - for listenerName in pairs(theseListeners) do - collisionListenerLimit:free(thisInstance.player, 1) - theseListeners[listenerName] = nil -- Faster GC - end - - theseListenersPerInstance[thisInstance] = nil -- Faster GC - end - - instanceCollisionListenersPerEnt[ent] = nil -- Signify that listeners have been removed - end) + collisionListeners:add(ent, name, func) end --- Removes a collision listener from the entity --- @param string? name The name of the collision listener to remove. If nil, will remove all listeners. +-- @param string? name The name of the collision listener to remove. (default: "") function ents_methods:removeCollisionListener(name) - local ent = getent(self) - if name ~= nil then checkluatype(name, TYPE_STRING) end - - local listenersPerInstance = instanceCollisionListenersPerEnt[ent] - if not listenersPerInstance then return end - - local listeners = listenersPerInstance[instance] - if not listeners then return end - - checkpermission(instance, ent, "entities.canTool") - - if name ~= nil then - if listeners[name] then - collisionListenerLimit:free(instance.player, 1) - listeners[name] = nil - end - else - for listenerName in pairs(listeners) do - collisionListenerLimit:free(instance.player, 1) - listeners[listenerName] = nil - end - end - - if next(listeners) ~= nil then return end - - listenersPerInstance[instance] = nil - entsWithCollisionListeners[ent] = nil + if name ~= nil then checkluatype(name, TYPE_STRING) else name = "" end - if next(listenersPerInstance) ~= nil then return end - - local oldPhysicsCollide = ent.SF_OldPhysicsCollide - - if oldPhysicsCollide then - ent.PhysicsCollide = oldPhysicsCollide - else - ent:RemoveCallback("PhysicsCollide", ent.SF_CollisionCallback) - end - - SF.RemoveCallOnRemove(ent, "RemoveCollisionListeners") - - instanceCollisionListenersPerEnt[ent] = nil -end - ---- Checks if an entity has a collision listener ---- Only checks for listeners created by this chip --- @param string? name The name of the collision listener to check for. If nil, will check if any listeners exist. -function ents_methods:hasCollisionListener( name ) local ent = getent(self) - if name ~= nil then checkluatype(name, TYPE_STRING) end - - local listenersPerInstance = instanceCollisionListenersPerEnt[ent] - if not listenersPerInstance then return false end - - local listeners = listenersPerInstance[instance] - if not listeners then return false end - if name == nil then return true end -- When listeners ~= nil, there's at least one listener - - return listeners[name] ~= nil -end - ---- Returns a table of all collision listener names ---- Only returns listeners created by this chip ---- Nameless listeners will not be included -function ents_methods:getCollisionListenerNames() - local ent = getent(self) - - local listenersPerInstance = instanceCollisionListenersPerEnt[ent] - if not listenersPerInstance then return {} end - - local listeners = listenersPerInstance[instance] - if not listeners then return {} end - - local names = {} - - for name in pairs(listeners) do - if type(name) == "string" then - names[#names + 1] = name - end - end + checkpermission(instance, ent, "entities.canTool") - return names + collisionListeners:remove(ent, name) end --- Sets whether an entity's shadow should be drawn From 96f377707948631942557c2de9c99c1d96751e9b Mon Sep 17 00:00:00 2001 From: thegrb93 Date: Tue, 8 Oct 2024 01:53:10 -0400 Subject: [PATCH 07/12] Fix whitespace --- lua/starfall/libs_sv/entities.lua | 282 +++++++++++++++--------------- 1 file changed, 141 insertions(+), 141 deletions(-) diff --git a/lua/starfall/libs_sv/entities.lua b/lua/starfall/libs_sv/entities.lua index c49847337..2a13a1961 100644 --- a/lua/starfall/libs_sv/entities.lua +++ b/lua/starfall/libs_sv/entities.lua @@ -35,88 +35,88 @@ local collisionListenerLimit = SF.LimitObject("collisionlistener", "collisionlis local base_physicscollide SF.GlobalCollisionListener = { __index = { - create = function(self, ent) - local listenertable = {} - - local queue = {} - local function collisionQueueProcess() - if IsValid(ent) then - for _, listener in ipairs(listenertable) do - local instance = listener.instance - for i=1, #queue do - listener:run(SF.StructWrapper(instance, queue[i], "CollisionData")) - end - end - end - for i=1, #queue do - queue[i] = nil - end - end - - local function collisionQueueCallback(ent, data) - local i = #queue+1 - queue[i] = data - if i==1 then timer.Simple(0, collisionQueueProcess) end - end - - if ent:IsScripted() then - local oldPhysicsCollide = ent.PhysicsCollide or base_physicscollide - ent.SF_OldPhysicsCollide = oldPhysicsCollide - - function ent:PhysicsCollide(data, phys) - oldPhysicsCollide(self, data, phys) - collisionQueueCallback(self, data) - end - else - ent.SF_CollisionCallback = ent:AddCallback("PhysicsCollide", collisionQueueCallback) - end - SF.CallOnRemove(ent, "RemoveCollisionListeners", function(e) self:destroy(e) end) - - self.listeners[ent] = listenertable - return listenertable - end, - destroy = function(self, ent) - local entlisteners = self.listeners[ent] - if entlisteners==nil then return end - self.listeners[ent] = nil - for _, listener in ipairs(entlisteners) do - listener.manager:free(ent, listener) - end - if IsValid(ent) then - local oldPhysicsCollide = ent.SF_OldPhysicsCollide - if oldPhysicsCollide then - ent.PhysicsCollide = oldPhysicsCollide - else - ent:RemoveCallback("PhysicsCollide", ent.SF_CollisionCallback) - end - - SF.RemoveCallOnRemove(ent, "RemoveCollisionListeners") - end - end, - add = function(self, ent, listener) - local entlisteners = self.listeners[ent] - if entlisteners == nil then - entlisteners = self:create(ent) - elseif table.HasValue(entlisteners, listener) then - return - end - entlisteners[#entlisteners + 1] = listener - end, - remove = function(self, ent, listener) - local entlisteners = self.listeners[ent] - if entlisteners==nil then return end - local i = table.HasValue(entlisteners, listener) - if i==nil then return end - - entlisteners[i] = entlisteners[#entlisteners] - entlisteners[#entlisteners] = nil - if entlisteners[1]==nil then self:destroy(ent) end - end + create = function(self, ent) + local listenertable = {} + + local queue = {} + local function collisionQueueProcess() + if IsValid(ent) then + for _, listener in ipairs(listenertable) do + local instance = listener.instance + for i=1, #queue do + listener:run(SF.StructWrapper(instance, queue[i], "CollisionData")) + end + end + end + for i=1, #queue do + queue[i] = nil + end + end + + local function collisionQueueCallback(ent, data) + local i = #queue+1 + queue[i] = data + if i==1 then timer.Simple(0, collisionQueueProcess) end + end + + if ent:IsScripted() then + local oldPhysicsCollide = ent.PhysicsCollide or base_physicscollide + ent.SF_OldPhysicsCollide = oldPhysicsCollide + + function ent:PhysicsCollide(data, phys) + oldPhysicsCollide(self, data, phys) + collisionQueueCallback(self, data) + end + else + ent.SF_CollisionCallback = ent:AddCallback("PhysicsCollide", collisionQueueCallback) + end + SF.CallOnRemove(ent, "RemoveCollisionListeners", function(e) self:destroy(e) end) + + self.listeners[ent] = listenertable + return listenertable + end, + destroy = function(self, ent) + local entlisteners = self.listeners[ent] + if entlisteners==nil then return end + self.listeners[ent] = nil + for _, listener in ipairs(entlisteners) do + listener.manager:free(ent, listener) + end + if IsValid(ent) then + local oldPhysicsCollide = ent.SF_OldPhysicsCollide + if oldPhysicsCollide then + ent.PhysicsCollide = oldPhysicsCollide + else + ent:RemoveCallback("PhysicsCollide", ent.SF_CollisionCallback) + end + + SF.RemoveCallOnRemove(ent, "RemoveCollisionListeners") + end + end, + add = function(self, ent, listener) + local entlisteners = self.listeners[ent] + if entlisteners == nil then + entlisteners = self:create(ent) + elseif table.HasValue(entlisteners, listener) then + return + end + entlisteners[#entlisteners + 1] = listener + end, + remove = function(self, ent, listener) + local entlisteners = self.listeners[ent] + if entlisteners==nil then return end + local i = table.HasValue(entlisteners, listener) + if i==nil then return end + + entlisteners[i] = entlisteners[#entlisteners] + entlisteners[#entlisteners] = nil + if entlisteners[1]==nil then self:destroy(ent) end + end }, __call = function(p) return setmetatable({ - listeners = {} - }, p) + listeners = {} + }, p) end } setmetatable(SF.GlobalCollisionListener, SF.GlobalCollisionListener) @@ -125,57 +125,57 @@ local globalListeners = SF.GlobalCollisionListener() SF.InstanceCollisionListener = { __index = { add = function(self, ent, name, func) - local created = false - local listener = self.hooksPerEnt[ent] - if listener==nil then - collisionListenerLimit:checkuse(self.instance.player, 1) - listener = SF.HookTable() - listener.manager = self - listener.instance = self.instance - self.hooksPerEnt[ent] = listener - - globalListeners:add(listener) - created = true - elseif not listener:exists(name) then - collisionListenerLimit:checkuse(self.instance.player, 1) - created = true - end - - listener:add(name, func) - - if created then - collisionListenerLimit:free(self.instance.player, -1) - end + local created = false + local listener = self.hooksPerEnt[ent] + if listener==nil then + collisionListenerLimit:checkuse(self.instance.player, 1) + listener = SF.HookTable() + listener.manager = self + listener.instance = self.instance + self.hooksPerEnt[ent] = listener + + globalListeners:add(listener) + created = true + elseif not listener:exists(name) then + collisionListenerLimit:checkuse(self.instance.player, 1) + created = true + end + + listener:add(name, func) + + if created then + collisionListenerLimit:free(self.instance.player, -1) + end + end, + remove = function(self, ent, name) + local listener = self.hooksPerEnt[ent] + if listener and listener:exists(name) then + listener:remove(name) + collisionListenerLimit:free(self.instance.player, 1) + if listener:isEmpty() then + globalListeners:remove(listener) + self.hooksPerEnt[ent] = nil + end + end end, - remove = function(self, ent, name) - local listener = self.hooksPerEnt[ent] - if listener and listener:exists(name) then - listener:remove(name) - collisionListenerLimit:free(self.instance.player, 1) - if listener:isEmpty() then - globalListeners:remove(listener) - self.hooksPerEnt[ent] = nil - end - end - end, - free = function(self, ent, listener) - collisionListenerLimit:free(self.instance.player, listener.n) - self.hooksPerEnt[ent] = nil - end, - destroy = function(self) - for ent, listener in pairs(self.hooksPerEnt) do - collisionListenerLimit:free(self.instance.player, listener.n) - self.hooksPerEnt[ent] = nil - - globalListeners:remove(listener) - end - end + free = function(self, ent, listener) + collisionListenerLimit:free(self.instance.player, listener.n) + self.hooksPerEnt[ent] = nil + end, + destroy = function(self) + for ent, listener in pairs(self.hooksPerEnt) do + collisionListenerLimit:free(self.instance.player, listener.n) + self.hooksPerEnt[ent] = nil + + globalListeners:remove(listener) + end + end }, __call = function(p, instance) return setmetatable({ - instance = instance, - hooksPerEnt = {} - }, p) + instance = instance, + hooksPerEnt = {} + }, p) end } setmetatable(SF.InstanceCollisionListener, SF.InstanceCollisionListener) @@ -510,12 +510,12 @@ end -- @param string? name Optional name to distinguish multiple collision listeners and remove them individually later. (default: "") function ents_methods:addCollisionListener(func, name) checkluatype(func, TYPE_FUNCTION) - if name ~= nil then checkluatype(name, TYPE_STRING) else name = "" end + if name ~= nil then checkluatype(name, TYPE_STRING) else name = "" end local ent = getent(self) checkpermission(instance, ent, "entities.canTool") - collisionListeners:add(ent, name, func) + collisionListeners:add(ent, name, func) end --- Removes a collision listener from the entity @@ -526,7 +526,7 @@ function ents_methods:removeCollisionListener(name) local ent = getent(self) checkpermission(instance, ent, "entities.canTool") - collisionListeners:remove(ent, name) + collisionListeners:remove(ent, name) end --- Sets whether an entity's shadow should be drawn @@ -637,11 +637,11 @@ end -- @param number? usetype The USE_ enum use type. (Default: USE_ON) -- @param number? value The use value (Default: 0) function ents_methods:use(usetype, value) - local ent = getent(self) - checkpermission(instance, ent, "entities.use") - if usetype~=nil then checkluatype(usetype, TYPE_NUMBER) end - if value~=nil then checkluatype(value, TYPE_NUMBER) end - ent:Use(instance.player, instance.entity, usetype, value) + local ent = getent(self) + checkpermission(instance, ent, "entities.use") + if usetype~=nil then checkluatype(usetype, TYPE_NUMBER) end + if value~=nil then checkluatype(value, TYPE_NUMBER) end + ent:Use(instance.player, instance.entity, usetype, value) end --- Sets the entity to be Solid or not. @@ -1073,19 +1073,19 @@ end --- Returns a copy of the entity's sanitized internal glua table. -- @return table The entity's table. function ents_methods:getTable() - local ent = getent(self) - checkpermission(instance, ent, "entities.getTable") - return instance.Sanitize(ent:GetTable()) + local ent = getent(self) + checkpermission(instance, ent, "entities.getTable") + return instance.Sanitize(ent:GetTable()) end --- Returns a variable from the entity's internal glua table. -- @param string key The variable's key. -- @return any The variable. function ents_methods:getVar(key) - local ent = getent(self) - checkpermission(instance, ent, "entities.getTable") - local var = ent:GetVar(key) - return istable(var) and instance.Sanitize(var) or owrap(var) + local ent = getent(self) + checkpermission(instance, ent, "entities.getTable") + local var = ent:GetVar(key) + return istable(var) and instance.Sanitize(var) or owrap(var) end end From a9542844085b7c3c2ca24d4de80d8e74460e27e6 Mon Sep 17 00:00:00 2001 From: thegrb93 Date: Tue, 8 Oct 2024 16:21:29 -0400 Subject: [PATCH 08/12] Fixes --- lua/starfall/libs_sv/entities.lua | 21 ++++++++++++--------- lua/starfall/sflib.lua | 3 +++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lua/starfall/libs_sv/entities.lua b/lua/starfall/libs_sv/entities.lua index 2a13a1961..063a6c382 100644 --- a/lua/starfall/libs_sv/entities.lua +++ b/lua/starfall/libs_sv/entities.lua @@ -30,10 +30,13 @@ registerprivilege("entities.canTool", "CanTool", "Whether or not the user can us registerprivilege("entities.use", "Use", "Whether or not the user can use the entity", { entities = {} }) registerprivilege("entities.getTable", "GetTable", "Allows the user to get an entity's table", { entities = {}, usergroups = { default = 1 } }) +local function table_find(tbl, val) + for i=1, #tbl do if tbl[i]==val then return i end end +end -local collisionListenerLimit = SF.LimitObject("collisionlistener", "collisionlistner", 200, "The number of concurrent starfall collision listeners") +local collisionListenerLimit = SF.LimitObject("collisionlistener", "collisionlistner", 128, "The number of concurrent starfall collision listeners") local base_physicscollide -SF.GlobalCollisionListener = { +SF.GlobalCollisionListeners = { __index = { create = function(self, ent) local listenertable = {} @@ -97,7 +100,7 @@ SF.GlobalCollisionListener = { local entlisteners = self.listeners[ent] if entlisteners == nil then entlisteners = self:create(ent) - elseif table.HasValue(entlisteners, listener) then + elseif table_find(entlisteners, listener) then return end entlisteners[#entlisteners + 1] = listener @@ -105,7 +108,7 @@ SF.GlobalCollisionListener = { remove = function(self, ent, listener) local entlisteners = self.listeners[ent] if entlisteners==nil then return end - local i = table.HasValue(entlisteners, listener) + local i = table_find(entlisteners, listener) if i==nil then return end entlisteners[i] = entlisteners[#entlisteners] @@ -119,10 +122,10 @@ SF.GlobalCollisionListener = { }, p) end } -setmetatable(SF.GlobalCollisionListener, SF.GlobalCollisionListener) -local globalListeners = SF.GlobalCollisionListener() +setmetatable(SF.GlobalCollisionListeners, SF.GlobalCollisionListeners) +local globalListeners = SF.GlobalCollisionListeners() -SF.InstanceCollisionListener = { +SF.InstanceCollisionListeners = { __index = { add = function(self, ent, name, func) local created = false @@ -178,7 +181,7 @@ SF.InstanceCollisionListener = { }, p) end } -setmetatable(SF.InstanceCollisionListener, SF.InstanceCollisionListener) +setmetatable(SF.InstanceCollisionListeners, SF.InstanceCollisionListeners) local function checkvector(v) if v[1]<-1e12 or v[1]>1e12 or v[1]~=v[1] or @@ -198,7 +201,7 @@ local ang_meta, awrap, aunwrap = instance.Types.Angle, instance.Types.Angle.Wrap local vec_meta, vwrap, vunwrap = instance.Types.Vector, instance.Types.Vector.Wrap, instance.Types.Vector.Unwrap local cunwrap = instance.Types.Color.Unwrap -local collisionListeners = SF.InstanceCollisionListener(instance) +local collisionListeners = SF.InstanceCollisionListeners(instance) base_physicscollide = baseclass.Get("base_gmodentity").PhysicsCollide local getent diff --git a/lua/starfall/sflib.lua b/lua/starfall/sflib.lua index 9fc8dc2cc..05e0574ee 100644 --- a/lua/starfall/sflib.lua +++ b/lua/starfall/sflib.lua @@ -909,6 +909,9 @@ do self.pairs = self.dirtyPairs end end, + exists = function(self, index) + return self.hooks[index]~=nil or self.hookstoadd[index]~=nil + end, isEmpty = function(self) return self.n==0 end, From 4ca182ec5d6eb051e39809f4d3c5600e71aa1ec1 Mon Sep 17 00:00:00 2001 From: thegrb93 Date: Tue, 8 Oct 2024 16:37:01 -0400 Subject: [PATCH 09/12] Fix --- lua/starfall/libs_sv/entities.lua | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lua/starfall/libs_sv/entities.lua b/lua/starfall/libs_sv/entities.lua index 063a6c382..67f8fb733 100644 --- a/lua/starfall/libs_sv/entities.lua +++ b/lua/starfall/libs_sv/entities.lua @@ -83,7 +83,7 @@ SF.GlobalCollisionListeners = { if entlisteners==nil then return end self.listeners[ent] = nil for _, listener in ipairs(entlisteners) do - listener.manager:free(ent, listener) + listener.manager:free(ent) end if IsValid(ent) then local oldPhysicsCollide = ent.SF_OldPhysicsCollide @@ -137,7 +137,7 @@ SF.InstanceCollisionListeners = { listener.instance = self.instance self.hooksPerEnt[ent] = listener - globalListeners:add(listener) + globalListeners:add(ent, listener) created = true elseif not listener:exists(name) then collisionListenerLimit:checkuse(self.instance.player, 1) @@ -156,21 +156,23 @@ SF.InstanceCollisionListeners = { listener:remove(name) collisionListenerLimit:free(self.instance.player, 1) if listener:isEmpty() then - globalListeners:remove(listener) self.hooksPerEnt[ent] = nil + globalListeners:remove(ent, listener) end end end, - free = function(self, ent, listener) - collisionListenerLimit:free(self.instance.player, listener.n) - self.hooksPerEnt[ent] = nil + free = function(self, ent) + local listener = self.hooksPerEnt[ent] + if listener then + collisionListenerLimit:free(self.instance.player, listener.n) + self.hooksPerEnt[ent] = nil + end end, destroy = function(self) for ent, listener in pairs(self.hooksPerEnt) do collisionListenerLimit:free(self.instance.player, listener.n) self.hooksPerEnt[ent] = nil - - globalListeners:remove(listener) + globalListeners:remove(ent, listener) end end }, From 0e313d5caac2ce24a45742bed2fb7145108dd4d9 Mon Sep 17 00:00:00 2001 From: thegrb93 Date: Tue, 8 Oct 2024 18:34:25 -0400 Subject: [PATCH 10/12] Add queue counter --- lua/starfall/libs_sv/entities.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lua/starfall/libs_sv/entities.lua b/lua/starfall/libs_sv/entities.lua index 67f8fb733..87d26512e 100644 --- a/lua/starfall/libs_sv/entities.lua +++ b/lua/starfall/libs_sv/entities.lua @@ -42,24 +42,26 @@ SF.GlobalCollisionListeners = { local listenertable = {} local queue = {} + local nqueue = 0 local function collisionQueueProcess() if IsValid(ent) then for _, listener in ipairs(listenertable) do local instance = listener.instance - for i=1, #queue do + for i=1, nqueue do listener:run(SF.StructWrapper(instance, queue[i], "CollisionData")) end end end - for i=1, #queue do + for i=1, nqueue do queue[i] = nil end + nqueue = 0 end local function collisionQueueCallback(ent, data) - local i = #queue+1 - queue[i] = data - if i==1 then timer.Simple(0, collisionQueueProcess) end + nqueue = nqueue + 1 + queue[nqueue] = data + if nqueue==1 then timer.Simple(0, collisionQueueProcess) end end if ent:IsScripted() then From 61705401e676d9dde7855c2f01b3e2dafd0acc78 Mon Sep 17 00:00:00 2001 From: thegrb93 Date: Wed, 9 Oct 2024 00:07:03 -0400 Subject: [PATCH 11/12] Got it working --- lua/starfall/libs_sv/entities.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/starfall/libs_sv/entities.lua b/lua/starfall/libs_sv/entities.lua index 87d26512e..526197008 100644 --- a/lua/starfall/libs_sv/entities.lua +++ b/lua/starfall/libs_sv/entities.lua @@ -48,7 +48,7 @@ SF.GlobalCollisionListeners = { for _, listener in ipairs(listenertable) do local instance = listener.instance for i=1, nqueue do - listener:run(SF.StructWrapper(instance, queue[i], "CollisionData")) + listener:run(instance, SF.StructWrapper(instance, queue[i], "CollisionData")) end end end From 62fec70d7f18644ece97f9a954b7ac0e5d810688 Mon Sep 17 00:00:00 2001 From: legokidlogan <26103433+legokidlogan@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:04:01 -0600 Subject: [PATCH 12/12] Cleanup --- lua/starfall/libs_sv/entities.lua | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lua/starfall/libs_sv/entities.lua b/lua/starfall/libs_sv/entities.lua index 526197008..f2b62a76f 100644 --- a/lua/starfall/libs_sv/entities.lua +++ b/lua/starfall/libs_sv/entities.lua @@ -6,9 +6,6 @@ local IsValid = FindMetaTable("Entity").IsValid local IsValidPhys = FindMetaTable("PhysObj").IsValid local isentity = isentity -local huge = math.huge -local abs = math.abs - -- Register privileges registerprivilege("entities.applyDamage", "Apply damage", "Allows the user to apply damage to an entity", { entities = {} }) registerprivilege("entities.applyForce", "Apply force", "Allows the user to apply force to an entity", { entities = {} }) @@ -40,7 +37,7 @@ SF.GlobalCollisionListeners = { __index = { create = function(self, ent) local listenertable = {} - + local queue = {} local nqueue = 0 local function collisionQueueProcess() @@ -57,17 +54,17 @@ SF.GlobalCollisionListeners = { end nqueue = 0 end - + local function collisionQueueCallback(ent, data) nqueue = nqueue + 1 queue[nqueue] = data if nqueue==1 then timer.Simple(0, collisionQueueProcess) end end - + if ent:IsScripted() then local oldPhysicsCollide = ent.PhysicsCollide or base_physicscollide ent.SF_OldPhysicsCollide = oldPhysicsCollide - + function ent:PhysicsCollide(data, phys) oldPhysicsCollide(self, data, phys) collisionQueueCallback(self, data) @@ -94,7 +91,7 @@ SF.GlobalCollisionListeners = { else ent:RemoveCallback("PhysicsCollide", ent.SF_CollisionCallback) end - + SF.RemoveCallOnRemove(ent, "RemoveCollisionListeners") end end,