mirror of
				https://github.com/Ukendio/jecs.git
				synced 2025-11-04 10:59:18 +00:00 
			
		
		
		
	Allow pre existing hooks for observer
This commit is contained in:
		
							parent
							
								
									48a43d4ff8
								
							
						
					
					
						commit
						150afd784a
					
				
					 5 changed files with 108 additions and 90 deletions
				
			
		| 
						 | 
				
			
			@ -6,9 +6,9 @@ type Observer<T...> = {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
export type PatchedWorld = jecs.World & {
 | 
			
		||||
	added: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: any) -> ()) -> (),
 | 
			
		||||
	removed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id) -> ()) -> (),
 | 
			
		||||
	changed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id) -> ()) -> (),
 | 
			
		||||
	added: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> () -> (),
 | 
			
		||||
	removed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id) -> ()) -> () -> (),
 | 
			
		||||
	changed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> () -> (),
 | 
			
		||||
	observer: (PatchedWorld, Observer<any>) -> (),
 | 
			
		||||
	monitor: (PatchedWorld, Observer<any>) -> (),
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +70,7 @@ local function join(world, component)
 | 
			
		|||
		sparse_array[entity] = nil
 | 
			
		||||
		dense_array[max_id] = nil
 | 
			
		||||
		values[max_id] = nil
 | 
			
		||||
		max_id -= 1
 | 
			
		||||
	end)
 | 
			
		||||
 | 
			
		||||
	world:changed(component, function(entity, id, value)
 | 
			
		||||
| 
						 | 
				
			
			@ -89,62 +90,6 @@ local function join(world, component)
 | 
			
		|||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function query_changed(world, component)
 | 
			
		||||
	assert(jecs.IS_PAIR(component) == false)
 | 
			
		||||
	local callerid = debug.info(2, "sl")
 | 
			
		||||
 | 
			
		||||
	local tracker = world.trackers[callerid]
 | 
			
		||||
	if not tracker then
 | 
			
		||||
		local records = {}
 | 
			
		||||
		local connections = {}
 | 
			
		||||
		tracker = {
 | 
			
		||||
			records = records,
 | 
			
		||||
			connections = connections
 | 
			
		||||
		}
 | 
			
		||||
		world.trackers[callerid] = tracker
 | 
			
		||||
 | 
			
		||||
		table.insert(connections, world:added(component, function(entity, id, v)
 | 
			
		||||
			tracker[entity] = {
 | 
			
		||||
				new = v
 | 
			
		||||
			}
 | 
			
		||||
		end))
 | 
			
		||||
		table.insert(connections, world:changed(component, function(entity, id, v)
 | 
			
		||||
			local record = tracker[entity]
 | 
			
		||||
			record.old = record.new
 | 
			
		||||
			record.new = v
 | 
			
		||||
		end))
 | 
			
		||||
 | 
			
		||||
		table.insert(connections, world:removed(component, function(entity, id)
 | 
			
		||||
			local record = tracker[entity]
 | 
			
		||||
			record.old = record.new
 | 
			
		||||
			record.new = nil
 | 
			
		||||
		end))
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local entity = nil
 | 
			
		||||
	local record = nil
 | 
			
		||||
	return function()
 | 
			
		||||
		entity, record = next(tracker, entity)
 | 
			
		||||
		if entity == nil then
 | 
			
		||||
			return
 | 
			
		||||
		end
 | 
			
		||||
		return entity, record
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function spy_on_world_delete(world)
 | 
			
		||||
	local world_delete = world.delete
 | 
			
		||||
	world.delete = function(world, entity)
 | 
			
		||||
		world_delete(world, entity)
 | 
			
		||||
		for _, tracker in world.trackers do
 | 
			
		||||
			tracker.records[entity] = nil
 | 
			
		||||
			for _, connection in tracker.connections do
 | 
			
		||||
				connection()
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function monitors_new(world, description)
 | 
			
		||||
	local query = description.query
 | 
			
		||||
	local callback = description.callback
 | 
			
		||||
| 
						 | 
				
			
			@ -192,18 +137,23 @@ local function monitors_new(world, description)
 | 
			
		|||
 	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function observers_add(world: jecs.World & { [string]: any }): PatchedWorld
 | 
			
		||||
local function observers_add(world: jecs.World): PatchedWorld
 | 
			
		||||
	type Signal = { [jecs.Entity]: { (...any) -> () } }
 | 
			
		||||
 | 
			
		||||
	local world_mut = world :: jecs.World & {[string]: any}
 | 
			
		||||
 | 
			
		||||
	local signals = {
 | 
			
		||||
		added = {} :: Signal,
 | 
			
		||||
		emplaced = {} :: Signal,
 | 
			
		||||
		removed = {} :: Signal
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	world.added = function(_, component, fn)
 | 
			
		||||
	world_mut.added = function<T>(
 | 
			
		||||
		_: jecs.World,
 | 
			
		||||
		component: jecs.Id<T>,
 | 
			
		||||
		fn: (e: jecs.Entity, id: jecs.Id, value: T) -> ()
 | 
			
		||||
	)
 | 
			
		||||
		local listeners = signals.added[component]
 | 
			
		||||
		local component_index = world.component_index :: jecs.ComponentIndex
 | 
			
		||||
		assert(component_index[component] == nil, "You cannot use hooks on components you intend to use this signal with")
 | 
			
		||||
		if not listeners then
 | 
			
		||||
			listeners = {}
 | 
			
		||||
			signals.added[component] = listeners
 | 
			
		||||
| 
						 | 
				
			
			@ -213,7 +163,16 @@ local function observers_add(world: jecs.World & { [string]: any }): PatchedWorl
 | 
			
		|||
					listener(entity, id, value)
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
			world:set(component, jecs.OnAdd, on_add)
 | 
			
		||||
			local idr = world.component_index[component]
 | 
			
		||||
			if idr then
 | 
			
		||||
				local idr_hook_existing = idr.hooks.on_add
 | 
			
		||||
				if idr_hook_existing then
 | 
			
		||||
					table.insert(listeners, idr_hook_existing)
 | 
			
		||||
				end
 | 
			
		||||
				idr.hooks.on_add = on_add :: any
 | 
			
		||||
			else
 | 
			
		||||
				world:set(component, jecs.OnAdd, on_add)
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
		table.insert(listeners, fn)
 | 
			
		||||
		return function()
 | 
			
		||||
| 
						 | 
				
			
			@ -224,10 +183,12 @@ local function observers_add(world: jecs.World & { [string]: any }): PatchedWorl
 | 
			
		|||
		end
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	world.changed = function(_, component, fn)
 | 
			
		||||
	world_mut.changed = function<T>(
 | 
			
		||||
		_: jecs.World,
 | 
			
		||||
		component: jecs.Id<T>,
 | 
			
		||||
		fn: (e: jecs.Entity, id: jecs.Id, value: T) -> ()
 | 
			
		||||
	)
 | 
			
		||||
		local listeners = signals.emplaced[component]
 | 
			
		||||
		local component_index = world.component_index :: jecs.ComponentIndex
 | 
			
		||||
		assert(component_index[component] == nil, "You cannot use hooks on components you intend to use this signal with")
 | 
			
		||||
		if not listeners then
 | 
			
		||||
			listeners = {}
 | 
			
		||||
			signals.emplaced[component] = listeners
 | 
			
		||||
| 
						 | 
				
			
			@ -236,7 +197,16 @@ local function observers_add(world: jecs.World & { [string]: any }): PatchedWorl
 | 
			
		|||
					listener(entity, id, value)
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
			world:set(component, jecs.OnChange, on_change)
 | 
			
		||||
			local idr = world.component_index[component]
 | 
			
		||||
			if idr then
 | 
			
		||||
				local idr_hook_existing = idr.hooks.on_change
 | 
			
		||||
				if idr_hook_existing then
 | 
			
		||||
					table.insert(listeners, idr_hook_existing)
 | 
			
		||||
				end
 | 
			
		||||
				idr.hooks.on_change = on_change :: any
 | 
			
		||||
			else
 | 
			
		||||
				world:set(component, jecs.OnChange, on_change)
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
		table.insert(listeners, fn)
 | 
			
		||||
		return function()
 | 
			
		||||
| 
						 | 
				
			
			@ -247,10 +217,12 @@ local function observers_add(world: jecs.World & { [string]: any }): PatchedWorl
 | 
			
		|||
		end
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	world.removed = function(_, component, fn)
 | 
			
		||||
	world_mut.removed = function<T>(
 | 
			
		||||
		_: jecs.World,
 | 
			
		||||
		component: jecs.Id<T>,
 | 
			
		||||
		fn: (e: jecs.Entity, id: jecs.Id) -> ()
 | 
			
		||||
	)
 | 
			
		||||
		local listeners = signals.removed[component]
 | 
			
		||||
		local component_index = world.component_index :: jecs.ComponentIndex
 | 
			
		||||
		assert(component_index[component] == nil, "You cannot use hooks on components you intend to use this signal with")
 | 
			
		||||
		if not listeners then
 | 
			
		||||
			listeners = {}
 | 
			
		||||
			signals.removed[component] = listeners
 | 
			
		||||
| 
						 | 
				
			
			@ -259,7 +231,16 @@ local function observers_add(world: jecs.World & { [string]: any }): PatchedWorl
 | 
			
		|||
					listener(entity, id, value)
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
			world:set(component, jecs.OnRemove, on_remove)
 | 
			
		||||
			local idr = world.component_index[component]
 | 
			
		||||
			if idr then
 | 
			
		||||
				local idr_hook_existing = idr.hooks.on_remove
 | 
			
		||||
				if idr_hook_existing then
 | 
			
		||||
					table.insert(listeners, idr_hook_existing)
 | 
			
		||||
				end
 | 
			
		||||
				idr.hooks.on_remove = on_remove :: any
 | 
			
		||||
			else
 | 
			
		||||
				world:set(component, jecs.OnRemove, on_remove)
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
		table.insert(listeners, fn)
 | 
			
		||||
		return function()
 | 
			
		||||
| 
						 | 
				
			
			@ -270,15 +251,15 @@ local function observers_add(world: jecs.World & { [string]: any }): PatchedWorl
 | 
			
		|||
		end
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	world.signals = signals
 | 
			
		||||
	world_mut.signals = signals
 | 
			
		||||
 | 
			
		||||
	world.observer = observers_new
 | 
			
		||||
	world_mut.observer = observers_new
 | 
			
		||||
 | 
			
		||||
	world.monitor = monitors_new
 | 
			
		||||
	world_mut.monitor = monitors_new
 | 
			
		||||
 | 
			
		||||
	world.trackers = {}
 | 
			
		||||
	world_mut.trackers = {}
 | 
			
		||||
 | 
			
		||||
	return world :: PatchedWorld
 | 
			
		||||
	return world_mut :: PatchedWorld
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
return observers_add
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
			
		||||
local collect = require(ReplicatedStorage.collect)
 | 
			
		||||
local types = require(ReplicatedStorage.types)
 | 
			
		||||
local ct = require(ReplicatedStorage.components)
 | 
			
		||||
local collect = require("../../ReplicatedStorage/collect")
 | 
			
		||||
local types = require("../../ReplicatedStorage/types")
 | 
			
		||||
local ct = require("../../ReplicatedStorage/components")
 | 
			
		||||
local Players = game:GetService("Players")
 | 
			
		||||
 | 
			
		||||
local player_added = collect(Players.PlayerAdded)
 | 
			
		||||
| 
						 | 
				
			
			@ -13,7 +12,8 @@ return function(world: types.World, dt: number)
 | 
			
		|||
 | 
			
		||||
	for entity, player in world:query(ct.Player):without(ct.Renderable) do
 | 
			
		||||
		local character = player.Character
 | 
			
		||||
		if character and character.Parent ~= nil then
 | 
			
		||||
		if character then
 | 
			
		||||
		if not character.Parent then
 | 
			
		||||
			world:set(entity, ct.Renderable, character)
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,24 @@ local observers_add = require("@addons/observers")
 | 
			
		|||
TEST("addons/observers", function()
 | 
			
		||||
	local world = observers_add(jecs.world())
 | 
			
		||||
 | 
			
		||||
	do CASE "Should not override hook"
 | 
			
		||||
		local A = world:component()
 | 
			
		||||
 | 
			
		||||
		local count = 0
 | 
			
		||||
		local function counter()
 | 
			
		||||
			count += 1
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		world:set(A, jecs.OnAdd, counter)
 | 
			
		||||
		world:set(world:entity(), A, true)
 | 
			
		||||
		CHECK(count == 1)
 | 
			
		||||
		world:added(A, counter)
 | 
			
		||||
		world:set(world:entity(), A, true)
 | 
			
		||||
 | 
			
		||||
		CHECK(count == 3)
 | 
			
		||||
		print(count)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	do CASE "Ensure ordering between signals and observers"
 | 
			
		||||
		local A = world:component()
 | 
			
		||||
		local B = world:component()
 | 
			
		||||
| 
						 | 
				
			
			@ -24,17 +42,12 @@ TEST("addons/observers", function()
 | 
			
		|||
		world:added(A, counter)
 | 
			
		||||
		world:added(A, counter)
 | 
			
		||||
 | 
			
		||||
		world:removed(A, counter)
 | 
			
		||||
 | 
			
		||||
		local e = world:entity()
 | 
			
		||||
		world:add(e, A)
 | 
			
		||||
		CHECK(count == 2)
 | 
			
		||||
 | 
			
		||||
		world:add(e, B)
 | 
			
		||||
		CHECK(count == 3)
 | 
			
		||||
 | 
			
		||||
		world:remove(e, A)
 | 
			
		||||
		CHECK(count == 4)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	do CASE "Rematch entities in observers"
 | 
			
		||||
| 
						 | 
				
			
			@ -87,10 +100,10 @@ TEST("addons/observers", function()
 | 
			
		|||
		local A = world:component()
 | 
			
		||||
 | 
			
		||||
		local callcount = 0
 | 
			
		||||
		world:added(A, function(entity) 
 | 
			
		||||
		world:added(A, function(entity)
 | 
			
		||||
			callcount += 1
 | 
			
		||||
		end)
 | 
			
		||||
		world:added(A, function(entity) 
 | 
			
		||||
		world:added(A, function(entity)
 | 
			
		||||
			callcount += 1
 | 
			
		||||
		end)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,30 @@ local entity_visualiser = require("@tools/entity_visualiser")
 | 
			
		|||
local lifetime_tracker_add = require("@tools/lifetime_tracker")
 | 
			
		||||
local dwi = entity_visualiser.stringify
 | 
			
		||||
 | 
			
		||||
TEST("repro", function()
 | 
			
		||||
	local Model = jecs.component()
 | 
			
		||||
	local Relation = jecs.component()
 | 
			
		||||
	local Relation2 = jecs.component()
 | 
			
		||||
 | 
			
		||||
	local world = jecs.world()
 | 
			
		||||
 | 
			
		||||
	local e1 = world:entity()
 | 
			
		||||
	world:set(e1, Model, 2)
 | 
			
		||||
 | 
			
		||||
	local e2 = world:entity()
 | 
			
		||||
	world:set(e2, Model, 2)
 | 
			
		||||
	world:set(e2, jecs.pair(Relation, e1), 5)
 | 
			
		||||
 | 
			
		||||
	local e3 = world:entity()
 | 
			
		||||
	world:set(e3, Model, 2)
 | 
			
		||||
	world:set(e3, jecs.pair(Relation, e1), 5)
 | 
			
		||||
 | 
			
		||||
	world:delete(e1)
 | 
			
		||||
 | 
			
		||||
	for _ in world:query(Model) do end
 | 
			
		||||
	jecs.ECS_META_RESET()
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
TEST("world:add()", function()
 | 
			
		||||
	do CASE "idempotent"
 | 
			
		||||
		local world = jecs.world()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "ukendio/jecs"
 | 
			
		||||
version = "0.5.5"
 | 
			
		||||
version = "0.6.0-rc.1"
 | 
			
		||||
registry = "https://github.com/UpliftGames/wally-index"
 | 
			
		||||
realm = "shared"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue