mirror of
				https://github.com/Ukendio/jecs.git
				synced 2025-11-04 10:59:18 +00:00 
			
		
		
		
	Example (#93)
* Initial commit * Return static function * Fix upvalues conflict * Add examples to luau * rename example folder * Add queries example * Add changetracking example * Add wildcards example * Delete example.project.json
This commit is contained in:
		
							parent
							
								
									e5634b10b2
								
							
						
					
					
						commit
						a9f449b3fb
					
				
					 13 changed files with 1041 additions and 0 deletions
				
			
		
							
								
								
									
										71
									
								
								demo.project.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								demo.project.json
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,71 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    "name": "demo",
 | 
				
			||||||
 | 
					    "tree": {
 | 
				
			||||||
 | 
					        "$className": "DataModel",
 | 
				
			||||||
 | 
					        "ReplicatedStorage": {
 | 
				
			||||||
 | 
					            "Shared": {
 | 
				
			||||||
 | 
					                "$path": "demo/src/shared"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "ecs": {
 | 
				
			||||||
 | 
					                "$path": "src"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "ServerScriptService": {
 | 
				
			||||||
 | 
					            "Server": {
 | 
				
			||||||
 | 
					                "$path": "demo/src/server"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "StarterPlayer": {
 | 
				
			||||||
 | 
					            "StarterPlayerScripts": {
 | 
				
			||||||
 | 
					                "Client": {
 | 
				
			||||||
 | 
					                    "$path": "demo/src/client"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "Workspace": {
 | 
				
			||||||
 | 
					            "$properties": {
 | 
				
			||||||
 | 
					                "FilteringEnabled": true
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "Baseplate": {
 | 
				
			||||||
 | 
					                "$className": "Part",
 | 
				
			||||||
 | 
					                "$properties": {
 | 
				
			||||||
 | 
					                    "Anchored": true,
 | 
				
			||||||
 | 
					                    "Color": [
 | 
				
			||||||
 | 
					                        0.38823,
 | 
				
			||||||
 | 
					                        0.37254,
 | 
				
			||||||
 | 
					                        0.38823
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                    "Locked": true,
 | 
				
			||||||
 | 
					                    "Position": [
 | 
				
			||||||
 | 
					                        0,
 | 
				
			||||||
 | 
					                        -10,
 | 
				
			||||||
 | 
					                        0
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                    "Size": [
 | 
				
			||||||
 | 
					                        512,
 | 
				
			||||||
 | 
					                        20,
 | 
				
			||||||
 | 
					                        512
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "Lighting": {
 | 
				
			||||||
 | 
					            "$properties": {
 | 
				
			||||||
 | 
					                "Ambient": [
 | 
				
			||||||
 | 
					                    0,
 | 
				
			||||||
 | 
					                    0,
 | 
				
			||||||
 | 
					                    0
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "Brightness": 2,
 | 
				
			||||||
 | 
					                "GlobalShadows": true,
 | 
				
			||||||
 | 
					                "Outlines": false,
 | 
				
			||||||
 | 
					                "Technology": "Voxel"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "SoundService": {
 | 
				
			||||||
 | 
					            "$properties": {
 | 
				
			||||||
 | 
					                "RespectFilteringEnabled": true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								demo/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								demo/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					# Project place file
 | 
				
			||||||
 | 
					/example.rbxlx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Roblox Studio lock files
 | 
				
			||||||
 | 
					/*.rbxlx.lock
 | 
				
			||||||
 | 
					/*.rbxl.lock
 | 
				
			||||||
							
								
								
									
										17
									
								
								demo/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								demo/README.md
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					# example
 | 
				
			||||||
 | 
					Generated by [Rojo](https://github.com/rojo-rbx/rojo) 7.4.1.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Getting Started
 | 
				
			||||||
 | 
					To build the place from scratch, use:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					rojo build -o "example.rbxlx"
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Next, open `example.rbxlx` in Roblox Studio and start the Rojo server:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					rojo serve
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For more help, check out [the Rojo documentation](https://rojo.space/docs).
 | 
				
			||||||
							
								
								
									
										1
									
								
								demo/src/client/init.client.luau
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								demo/src/client/init.client.luau
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					print("Hello world, from client!")
 | 
				
			||||||
							
								
								
									
										1
									
								
								demo/src/server/init.server.luau
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								demo/src/server/init.server.luau
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										258
									
								
								demo/src/shared/common.luau
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								demo/src/shared/common.luau
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,258 @@
 | 
				
			||||||
 | 
					--!optimize 2
 | 
				
			||||||
 | 
					--!native
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local jecs = require(game:GetService("ReplicatedStorage").ecs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type World = jecs.WorldShim
 | 
				
			||||||
 | 
					type Entity<T = any> = jecs.Entity<T>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function panic(str)
 | 
				
			||||||
 | 
					    -- We don't want to interrupt the loop when we error
 | 
				
			||||||
 | 
					    task.spawn(error, str)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function Scheduler(world, ...)
 | 
				
			||||||
 | 
					    local systems = { ... }
 | 
				
			||||||
 | 
					    local systemsNames = {}
 | 
				
			||||||
 | 
					    local N = #systems
 | 
				
			||||||
 | 
					    local system
 | 
				
			||||||
 | 
					    local dt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for i, module in systems do
 | 
				
			||||||
 | 
					        local sys = require(module)
 | 
				
			||||||
 | 
					        systems[i] = sys
 | 
				
			||||||
 | 
					        local file, line = debug.info(2, "sl")
 | 
				
			||||||
 | 
					        systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}`
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function run()
 | 
				
			||||||
 | 
					        local name = systemsNames[system]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        debug.profilebegin(name)
 | 
				
			||||||
 | 
					        debug.setmemorycategory(name)
 | 
				
			||||||
 | 
					        system(world, dt)
 | 
				
			||||||
 | 
					        debug.profileend()
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function loop(sinceLastFrame)
 | 
				
			||||||
 | 
					        debug.profilebegin("loop()")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i = N, 1, -1 do
 | 
				
			||||||
 | 
					            system = systems[i]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            dt = sinceLastFrame
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            local didNotYield, why = xpcall(function()
 | 
				
			||||||
 | 
					                for _ in run do end
 | 
				
			||||||
 | 
					            end, debug.traceback)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if didNotYield then
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if string.find(why, "thread is not yieldable") then
 | 
				
			||||||
 | 
									N -= 1
 | 
				
			||||||
 | 
									local name = table.remove(systems, i)
 | 
				
			||||||
 | 
									panic("Not allowed to yield in the systems."
 | 
				
			||||||
 | 
					    				.. "\n"
 | 
				
			||||||
 | 
					    				.. `System: {name} has been ejected`
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								    panic(why)
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        debug.profileend()
 | 
				
			||||||
 | 
					        debug.resetmemorycategory()
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return loop
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Tracker<T> = { track: (world: World, fn: (changes: {
 | 
				
			||||||
 | 
					        added: () -> () -> (number, T),
 | 
				
			||||||
 | 
					        removed: () -> () -> number,
 | 
				
			||||||
 | 
					        changed: () -> () -> (number, T, T)
 | 
				
			||||||
 | 
					    }) -> ()) -> ()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Entity<T = any> = number & { __nominal_type_dont_use: T }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function diff(a, b)
 | 
				
			||||||
 | 
					    local size = 0
 | 
				
			||||||
 | 
					    for k, v in a do
 | 
				
			||||||
 | 
					        if b[k] ~= v then
 | 
				
			||||||
 | 
					            return true
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        size += 1
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    for k, v in b do
 | 
				
			||||||
 | 
					        size -= 1
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if size ~= 0 then
 | 
				
			||||||
 | 
					        return true
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
 | 
				
			||||||
 | 
					    local PreviousT = jecs.pair(jecs.Rest, T)
 | 
				
			||||||
 | 
					    local add = {}
 | 
				
			||||||
 | 
					    local added
 | 
				
			||||||
 | 
					    local removed
 | 
				
			||||||
 | 
					    local is_trivial
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function changes_added()
 | 
				
			||||||
 | 
					        added = true
 | 
				
			||||||
 | 
					        local q = world:query(T):without(PreviousT):drain()
 | 
				
			||||||
 | 
					        return function()
 | 
				
			||||||
 | 
					            local id, data = q.next()
 | 
				
			||||||
 | 
					            if not id then
 | 
				
			||||||
 | 
					                return nil
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            is_trivial = typeof(data) ~= "table"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            add[id] = data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return id, data
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function changes_changed()
 | 
				
			||||||
 | 
					        local q = world:query(T, PreviousT):drain()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return function()
 | 
				
			||||||
 | 
					            local id, new, old = q.next()
 | 
				
			||||||
 | 
					            while true do
 | 
				
			||||||
 | 
					                if not id then
 | 
				
			||||||
 | 
					                    return nil
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if not is_trivial then
 | 
				
			||||||
 | 
					                    if diff(new, old) then
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					                    end
 | 
				
			||||||
 | 
					                elseif new ~= old then
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                id, new, old = q.next()
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            local record = world.entityIndex.sparse[id]
 | 
				
			||||||
 | 
					            local archetype = record.archetype
 | 
				
			||||||
 | 
					            local column = archetype.records[PreviousT].column
 | 
				
			||||||
 | 
					            local data = if is_trivial then new else table.clone(new)
 | 
				
			||||||
 | 
					            archetype.columns[column][record.row] = data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return id, old, new
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function changes_removed()
 | 
				
			||||||
 | 
					        removed = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        local q = world:query(PreviousT):without(T):drain()
 | 
				
			||||||
 | 
					        return function()
 | 
				
			||||||
 | 
					            local id = q.next()
 | 
				
			||||||
 | 
					            if id then
 | 
				
			||||||
 | 
					                world:remove(id, PreviousT)
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					            return id
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local changes = {
 | 
				
			||||||
 | 
					        added = changes_added,
 | 
				
			||||||
 | 
					        changed = changes_changed,
 | 
				
			||||||
 | 
					        removed = changes_removed,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function track(fn)
 | 
				
			||||||
 | 
					        added = false
 | 
				
			||||||
 | 
					        removed = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn(changes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not added then
 | 
				
			||||||
 | 
					            for _ in changes_added() do
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not removed then
 | 
				
			||||||
 | 
					            for _ in changes_removed() do
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for e, data in add do
 | 
				
			||||||
 | 
					            world:set(e, PreviousT, if is_trivial then data else table.clone(data))
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local tracker = { track = track }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return tracker
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local bt
 | 
				
			||||||
 | 
					do
 | 
				
			||||||
 | 
					    local SUCCESS = 0
 | 
				
			||||||
 | 
					    local FAILURE = 1
 | 
				
			||||||
 | 
					    local RUNNING = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function SEQUENCE(nodes)
 | 
				
			||||||
 | 
					        return function(...)
 | 
				
			||||||
 | 
					            for _, node in nodes do
 | 
				
			||||||
 | 
					                local status = node(...)
 | 
				
			||||||
 | 
					                if status == FAILURE or status == RUNNING then
 | 
				
			||||||
 | 
					                    return status
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					            return SUCCESS
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    local function FALLBACK(nodes)
 | 
				
			||||||
 | 
					        return function(...)
 | 
				
			||||||
 | 
					            for _, node in nodes do
 | 
				
			||||||
 | 
					                local status = node(...)
 | 
				
			||||||
 | 
					                if status == SUCCESS or status == RUNNING then
 | 
				
			||||||
 | 
					                    return status
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					            return FAILURE
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    bt = {
 | 
				
			||||||
 | 
					        SEQUENCE = SEQUENCE,
 | 
				
			||||||
 | 
					        FALLBACK = FALLBACK,
 | 
				
			||||||
 | 
					        RUNNING = RUNNING
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function interval(s)
 | 
				
			||||||
 | 
					    local pin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function throttle()
 | 
				
			||||||
 | 
					        if not pin then
 | 
				
			||||||
 | 
					            pin = os.clock()
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        local elapsed = os.clock() - pin > s
 | 
				
			||||||
 | 
					        if elapsed then
 | 
				
			||||||
 | 
					            pin = os.clock()
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return elapsed
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    return throttle
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return {
 | 
				
			||||||
 | 
					    Scheduler = Scheduler,
 | 
				
			||||||
 | 
					    ChangeTracker = ChangeTracker,
 | 
				
			||||||
 | 
					    interval = interval,
 | 
				
			||||||
 | 
					    BehaviorTree = bt
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								examples/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								examples/README.md
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					# Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This folder contains code examples for the Luau/Typescript APIs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Run with Luau
 | 
				
			||||||
 | 
					To run the examples with Luau, run the following commands from the root of the repository:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```sh
 | 
				
			||||||
 | 
					cd examples/luau
 | 
				
			||||||
 | 
					luau path/to/file.luau
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
							
								
								
									
										45
									
								
								examples/luau/entities/basics.luau
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								examples/luau/entities/basics.luau
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,45 @@
 | 
				
			||||||
 | 
					local jecs = require("@jecs")
 | 
				
			||||||
 | 
					local world = jecs.World.new()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local Position = world:component()
 | 
				
			||||||
 | 
					local Walking = world:component()
 | 
				
			||||||
 | 
					local Name = world:component()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Create an entity with name Bob
 | 
				
			||||||
 | 
					local bob = world:entity()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- The set operation finds or creates a component, and sets it.
 | 
				
			||||||
 | 
					world:set(bob, Position, Vector3.new(10, 20, 30))
 | 
				
			||||||
 | 
					-- Name the entity Bob
 | 
				
			||||||
 | 
					world:set(bob, Name, "Bob")
 | 
				
			||||||
 | 
					-- The add operation adds a component without setting a value. This is
 | 
				
			||||||
 | 
					-- useful for tags, or when adding a component with its default value.
 | 
				
			||||||
 | 
					world:add(bob, Walking)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Get the value for the Position component
 | 
				
			||||||
 | 
					local pos = world:get(bob, Position)
 | 
				
			||||||
 | 
					print(`\{{pos.X}, {pos.Y}, {pos.Z}\}`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Overwrite the value of the Position component
 | 
				
			||||||
 | 
					world:set(bob, Position, Vector3.new(40, 50, 60))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local alice = world:entity()
 | 
				
			||||||
 | 
					-- Create another named entity
 | 
				
			||||||
 | 
					world:set(alice, Name, "Alice")
 | 
				
			||||||
 | 
					world:set(alice, Position, Vector3.new(10, 20, 30))
 | 
				
			||||||
 | 
					world:add(alice, Walking)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Remove tag
 | 
				
			||||||
 | 
					world:remove(alice, Walking)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Iterate all entities with Position
 | 
				
			||||||
 | 
					for entity, p in world:query(Position) do
 | 
				
			||||||
 | 
					    print(`{entity}: \{{p.X}, {p.Y}, {p.Z}\}`)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Output:
 | 
				
			||||||
 | 
					--  {10, 20, 30}
 | 
				
			||||||
 | 
					--  Alice: {10, 20, 30}
 | 
				
			||||||
 | 
					--  Bob: {40, 50, 60}
 | 
				
			||||||
							
								
								
									
										125
									
								
								examples/luau/entities/hierarchy.luau
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								examples/luau/entities/hierarchy.luau
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,125 @@
 | 
				
			||||||
 | 
					local jecs = require("@jecs")
 | 
				
			||||||
 | 
					local pair = jecs.pair
 | 
				
			||||||
 | 
					local ChildOf = jecs.ChildOf
 | 
				
			||||||
 | 
					local world = jecs.World.new()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local Name = world:component()
 | 
				
			||||||
 | 
					local Position = world:component()
 | 
				
			||||||
 | 
					local Star = world:component()
 | 
				
			||||||
 | 
					local Planet = world:component()
 | 
				
			||||||
 | 
					local Moon = world:component()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local Vector3
 | 
				
			||||||
 | 
					do
 | 
				
			||||||
 | 
					    Vector3 = {}
 | 
				
			||||||
 | 
					    Vector3.__index = Vector3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function Vector3.new(x, y, z)
 | 
				
			||||||
 | 
					        x = x or 0
 | 
				
			||||||
 | 
					        y = y or 0
 | 
				
			||||||
 | 
					        z = z or 0
 | 
				
			||||||
 | 
					        return setmetatable({ X = x, Y = y, Z = z }, Vector3)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function Vector3.__add(left, right)
 | 
				
			||||||
 | 
					        return Vector3.new(
 | 
				
			||||||
 | 
					            left.X + right.X,
 | 
				
			||||||
 | 
					            left.Y + right.Y,
 | 
				
			||||||
 | 
					            left.Z + right.Z
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function Vector3.__mul(left, right)
 | 
				
			||||||
 | 
					        if typeof(right) == "number" then
 | 
				
			||||||
 | 
					            return Vector3.new(
 | 
				
			||||||
 | 
					                left.X * right,
 | 
				
			||||||
 | 
					                left.Y * right,
 | 
				
			||||||
 | 
					                left.Z * right
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return Vector3.new(
 | 
				
			||||||
 | 
					            left.X * right.X,
 | 
				
			||||||
 | 
					            left.Y * right.Y,
 | 
				
			||||||
 | 
					            left.Z * right.Z
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Vector3.one = Vector3.new(1, 1, 1)
 | 
				
			||||||
 | 
					    Vector3.zero = Vector3.new()
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function path(entity)
 | 
				
			||||||
 | 
					    local str = world:get(entity, Name)
 | 
				
			||||||
 | 
					    local parent
 | 
				
			||||||
 | 
					    while true do
 | 
				
			||||||
 | 
					        parent = world:parent(entity)
 | 
				
			||||||
 | 
					        if not parent then
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        entity = parent
 | 
				
			||||||
 | 
					        str = world:get(parent, Name) .. "/" .. str
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    return str
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function iterate(entity, parent)
 | 
				
			||||||
 | 
					    local p = world:get(entity, Position)
 | 
				
			||||||
 | 
					    local actual = p + parent
 | 
				
			||||||
 | 
					    print(path(entity))
 | 
				
			||||||
 | 
					    print(`\{{actual.X}, {actual.Y}, {actual.Z}}`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for child in world:query(pair(ChildOf, entity)) do
 | 
				
			||||||
 | 
					        --print(world:get(child, Name))
 | 
				
			||||||
 | 
					        iterate(child, actual)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local sun = world:entity()
 | 
				
			||||||
 | 
					world:add(sun, Star)
 | 
				
			||||||
 | 
					world:set(sun, Position, Vector3.one)
 | 
				
			||||||
 | 
					world:set(sun, Name, "Sun")
 | 
				
			||||||
 | 
					do
 | 
				
			||||||
 | 
					    local earth = world:entity()
 | 
				
			||||||
 | 
					    world:set(earth, Name, "Earth")
 | 
				
			||||||
 | 
					    world:add(earth, pair(ChildOf, sun))
 | 
				
			||||||
 | 
					    world:add(earth, Planet)
 | 
				
			||||||
 | 
					    world:set(earth, Position, Vector3.one * 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do
 | 
				
			||||||
 | 
					        local moon = world:entity()
 | 
				
			||||||
 | 
					        world:set(moon, Name, "Moon")
 | 
				
			||||||
 | 
					        world:add(moon, pair(ChildOf, earth))
 | 
				
			||||||
 | 
					        world:add(moon, Moon)
 | 
				
			||||||
 | 
					        world:set(moon, Position, Vector3.one * 0.1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        print(`Child of Earth? {world:has(moon, pair(ChildOf, earth))}`)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local venus = world:entity()
 | 
				
			||||||
 | 
					    world:set(venus, Name, "Venus")
 | 
				
			||||||
 | 
					    world:add(venus, pair(ChildOf, sun))
 | 
				
			||||||
 | 
					    world:add(venus, Planet)
 | 
				
			||||||
 | 
					    world:set(venus, Position, Vector3.one * 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local mercury = world:entity()
 | 
				
			||||||
 | 
					    world:set(mercury, Name, "Mercury")
 | 
				
			||||||
 | 
					    world:add(mercury, pair(ChildOf, sun))
 | 
				
			||||||
 | 
					    world:add(mercury, Planet)
 | 
				
			||||||
 | 
					    world:set(mercury, Position, Vector3.one)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    iterate(sun, Vector3.zero)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Output:
 | 
				
			||||||
 | 
					--  Child of Earth? true
 | 
				
			||||||
 | 
					--  Sun
 | 
				
			||||||
 | 
					--  {1, 1, 1}
 | 
				
			||||||
 | 
					--  Sun/Mercury
 | 
				
			||||||
 | 
					--  {2, 2, 2}
 | 
				
			||||||
 | 
					--  Sun/Venus
 | 
				
			||||||
 | 
					--  {3, 3, 3}
 | 
				
			||||||
 | 
					--  Sun/Earth
 | 
				
			||||||
 | 
					--  {4, 4, 4}
 | 
				
			||||||
 | 
					--  Sun/Earth/Moon
 | 
				
			||||||
 | 
					--  {4.1, 4.1, 4.1}
 | 
				
			||||||
							
								
								
									
										75
									
								
								examples/luau/queries/basics.luau
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								examples/luau/queries/basics.luau
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,75 @@
 | 
				
			||||||
 | 
					local jecs = require("@jecs")
 | 
				
			||||||
 | 
					local world = jecs.World.new()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local Position = world:component()
 | 
				
			||||||
 | 
					local Velocity = world:component()
 | 
				
			||||||
 | 
					local Name = world:component()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local Vector3
 | 
				
			||||||
 | 
					do
 | 
				
			||||||
 | 
					    Vector3 = {}
 | 
				
			||||||
 | 
					    Vector3.__index = Vector3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function Vector3.new(x, y, z)
 | 
				
			||||||
 | 
					        x = x or 0
 | 
				
			||||||
 | 
					        y = y or 0
 | 
				
			||||||
 | 
					        z = z or 0
 | 
				
			||||||
 | 
					        return setmetatable({ X = x, Y = y, Z = z }, Vector3)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function Vector3.__add(left, right)
 | 
				
			||||||
 | 
					        return Vector3.new(
 | 
				
			||||||
 | 
					            left.X + right.X,
 | 
				
			||||||
 | 
					            left.Y + right.Y,
 | 
				
			||||||
 | 
					            left.Z + right.Z
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function Vector3.__mul(left, right)
 | 
				
			||||||
 | 
					        if typeof(right) == "number" then
 | 
				
			||||||
 | 
					            return Vector3.new(
 | 
				
			||||||
 | 
					                left.X * right,
 | 
				
			||||||
 | 
					                left.Y * right,
 | 
				
			||||||
 | 
					                left.Z * right
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return Vector3.new(
 | 
				
			||||||
 | 
					            left.X * right.X,
 | 
				
			||||||
 | 
					            left.Y * right.Y,
 | 
				
			||||||
 | 
					            left.Z * right.Z
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Vector3.one = Vector3.new(1, 1, 1)
 | 
				
			||||||
 | 
					    Vector3.zero = Vector3.new()
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Create a few test entities for a Position, Velocity query
 | 
				
			||||||
 | 
					local e1 = world:entity()
 | 
				
			||||||
 | 
					world:set(e1, Name, "e1")
 | 
				
			||||||
 | 
					world:set(e1, Position, Vector3.new(10, 20, 30))
 | 
				
			||||||
 | 
					world:set(e1, Velocity, Vector3.new(1, 2, 3))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local e2 = world:entity()
 | 
				
			||||||
 | 
					world:set(e2, Name, "e2")
 | 
				
			||||||
 | 
					world:set(e2, Position, Vector3.new(10, 20, 30))
 | 
				
			||||||
 | 
					world:set(e2, Velocity, Vector3.new(4, 5, 6))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- This entity will not match as it does not have Position, Velocity
 | 
				
			||||||
 | 
					local e3 = world:entity()
 | 
				
			||||||
 | 
					world:set(e3, Name, "e3")
 | 
				
			||||||
 | 
					world:set(e3, Position, Vector3.new(10, 20, 30))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Create an uncached query for Position, Velocity.
 | 
				
			||||||
 | 
					for entity, p, v in world:query(Position, Velocity) do
 | 
				
			||||||
 | 
					    -- Iterate entities matching the query
 | 
				
			||||||
 | 
					    p.X += v.X
 | 
				
			||||||
 | 
					    p.Y += v.Y
 | 
				
			||||||
 | 
					    p.Z += v.Z
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print(`{world:get(entity, Name)}: \{{p.X}, {p.Y}, {p.Z}}`)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Output:
 | 
				
			||||||
 | 
					--  e2: {14, 25, 36}
 | 
				
			||||||
 | 
					--  e1: {11, 22, 33}
 | 
				
			||||||
							
								
								
									
										242
									
								
								examples/luau/queries/changetracking.luau
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								examples/luau/queries/changetracking.luau
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,242 @@
 | 
				
			||||||
 | 
					local jecs = require("@jecs")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type World = jecs.WorldShim
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Tracker<T> = { track: (world: World, fn: (changes: {
 | 
				
			||||||
 | 
					        added: () -> () -> (number, T),
 | 
				
			||||||
 | 
					        removed: () -> () -> number,
 | 
				
			||||||
 | 
					        changed: () -> () -> (number, T, T)
 | 
				
			||||||
 | 
					    }) -> ()) -> ()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function diff(a, b)
 | 
				
			||||||
 | 
					    local size = 0
 | 
				
			||||||
 | 
					    for k, v in a do
 | 
				
			||||||
 | 
					        if b[k] ~= v then
 | 
				
			||||||
 | 
					            return true
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        size += 1
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    for k, v in b do
 | 
				
			||||||
 | 
					        size -= 1
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if size ~= 0 then
 | 
				
			||||||
 | 
					        return true
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Entity<T> = number & { __nominal_type_dont_use: T }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
 | 
				
			||||||
 | 
					    local PreviousT = jecs.pair(jecs.Rest, T)
 | 
				
			||||||
 | 
					    local add = {}
 | 
				
			||||||
 | 
					    local added
 | 
				
			||||||
 | 
					    local removed
 | 
				
			||||||
 | 
					    local is_trivial
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function changes_added()
 | 
				
			||||||
 | 
					        added = true
 | 
				
			||||||
 | 
					        local q = world:query(T):without(PreviousT):drain()
 | 
				
			||||||
 | 
					        return function()
 | 
				
			||||||
 | 
					            local id, data = q.next()
 | 
				
			||||||
 | 
					            if not id then
 | 
				
			||||||
 | 
					                return nil
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            is_trivial = typeof(data) ~= "table"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            add[id] = data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return id, data
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function changes_changed()
 | 
				
			||||||
 | 
					        local q = world:query(T, PreviousT):drain()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return function()
 | 
				
			||||||
 | 
					            local id, new, old = q.next()
 | 
				
			||||||
 | 
					            while true do
 | 
				
			||||||
 | 
					                if not id then
 | 
				
			||||||
 | 
					                    return nil
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if not is_trivial then
 | 
				
			||||||
 | 
					                    if diff(new, old) then
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					                    end
 | 
				
			||||||
 | 
					                elseif new ~= old then
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                id, new, old = q.next()
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            add[id] = new
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return id, old, new
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function changes_removed()
 | 
				
			||||||
 | 
					        removed = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        local q = world:query(PreviousT):without(T):drain()
 | 
				
			||||||
 | 
					        return function()
 | 
				
			||||||
 | 
					            local id = q.next()
 | 
				
			||||||
 | 
					            if id then
 | 
				
			||||||
 | 
					                world:remove(id, PreviousT)
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					            return id
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local changes = {
 | 
				
			||||||
 | 
					        added = changes_added,
 | 
				
			||||||
 | 
					        changed = changes_changed,
 | 
				
			||||||
 | 
					        removed = changes_removed,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function track(fn)
 | 
				
			||||||
 | 
					        added = false
 | 
				
			||||||
 | 
					        removed = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn(changes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not added then
 | 
				
			||||||
 | 
					            for _ in changes_added() do
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not removed then
 | 
				
			||||||
 | 
					            for _ in changes_removed() do
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for e, data in add do
 | 
				
			||||||
 | 
					            world:set(e, PreviousT, if is_trivial then data else table.clone(data))
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local tracker = { track = track }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return tracker
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local Vector3
 | 
				
			||||||
 | 
					do
 | 
				
			||||||
 | 
					    Vector3 = {}
 | 
				
			||||||
 | 
					    Vector3.__index = Vector3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function Vector3.new(x, y, z)
 | 
				
			||||||
 | 
					        x = x or 0
 | 
				
			||||||
 | 
					        y = y or 0
 | 
				
			||||||
 | 
					        z = z or 0
 | 
				
			||||||
 | 
					        return setmetatable({ X = x, Y = y, Z = z }, Vector3)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function Vector3.__add(left, right)
 | 
				
			||||||
 | 
					        return Vector3.new(
 | 
				
			||||||
 | 
					            left.X + right.X,
 | 
				
			||||||
 | 
					            left.Y + right.Y,
 | 
				
			||||||
 | 
					            left.Z + right.Z
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function Vector3.__mul(left, right)
 | 
				
			||||||
 | 
					        if typeof(right) == "number" then
 | 
				
			||||||
 | 
					            return Vector3.new(
 | 
				
			||||||
 | 
					                left.X * right,
 | 
				
			||||||
 | 
					                left.Y * right,
 | 
				
			||||||
 | 
					                left.Z * right
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return Vector3.new(
 | 
				
			||||||
 | 
					            left.X * right.X,
 | 
				
			||||||
 | 
					            left.Y * right.Y,
 | 
				
			||||||
 | 
					            left.Z * right.Z
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Vector3.one = Vector3.new(1, 1, 1)
 | 
				
			||||||
 | 
					    Vector3.zero = Vector3.new()
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local world = jecs.World.new()
 | 
				
			||||||
 | 
					local Name = world:component()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function named(ctr, name)
 | 
				
			||||||
 | 
					    local e = ctr(world)
 | 
				
			||||||
 | 
					    world:set(e, Name, name)
 | 
				
			||||||
 | 
					    return e
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					local function name(e)
 | 
				
			||||||
 | 
					    return world:get(e, Name)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local Position = named(world.component, "Position")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Create the ChangeTracker with the component type to track
 | 
				
			||||||
 | 
					local PositionTracker = ChangeTracker(world, Position)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local e1 = named(world.entity, "e1")
 | 
				
			||||||
 | 
					world:set(e1, Position, Vector3.new(10, 20, 30))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local e2 = named(world.entity, "e2")
 | 
				
			||||||
 | 
					world:set(e2, Position, Vector3.new(10, 20, 30))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PositionTracker.track(function(changes)
 | 
				
			||||||
 | 
					    -- You can iterate over different types of changes: Added, Changed, Removed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    -- added queries for every entity with a new Position component
 | 
				
			||||||
 | 
					    for e, p in changes.added() do
 | 
				
			||||||
 | 
					        print(`Added {e}: \{{p.X}, {p.Y}, {p.Z}}`)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    -- changed queries for entities who's changed their data since
 | 
				
			||||||
 | 
					    -- last was it tracked
 | 
				
			||||||
 | 
					    for _ in changes.changed() do
 | 
				
			||||||
 | 
					        print([[This won't print because it is the first time
 | 
				
			||||||
 | 
					                we are tracking the Position component]])
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    -- removed queries for entities who's removed their Position component
 | 
				
			||||||
 | 
					    -- since last it was tracked
 | 
				
			||||||
 | 
					    for _ in changes.removed() do
 | 
				
			||||||
 | 
					        print([[This won't print because it is the first time
 | 
				
			||||||
 | 
					                we are tracking the Position component]])
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					world:set(e1, Position, Vector3.new(1, 1, 2) * 999)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PositionTracker.track(function(changes)
 | 
				
			||||||
 | 
					    for e, p in changes.added() do
 | 
				
			||||||
 | 
					        print([[This won't never print no Position component was added
 | 
				
			||||||
 | 
					                since last time we tracked]])
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for e, old, new in changes.changed() do
 | 
				
			||||||
 | 
					        print(`{name(e)}'s Position changed from \{{old.X}, {old.Y}, {old.Z}\} to \{{new.X}, {new.Y}, {new.Z}\}`)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    -- If you don't call e.g. changes.removed() then it will automatically drain its iterator and stage their changes.
 | 
				
			||||||
 | 
					    -- This ensures you will not have any off-by-one frame errors.
 | 
				
			||||||
 | 
					end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					world:remove(e2, Position)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PositionTracker.track(function(changes)
 | 
				
			||||||
 | 
					    for e in changes.removed() do
 | 
				
			||||||
 | 
					        print(`Position was removed from {name(e)}`)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Output:
 | 
				
			||||||
 | 
					--  Added 265: {10, 20, 30}
 | 
				
			||||||
 | 
					--  Added 264: {10, 20, 30}
 | 
				
			||||||
 | 
					--  e1's Position changed from {10, 20, 30} to {999, 999, 1998}
 | 
				
			||||||
 | 
					--  Position was removed from e2
 | 
				
			||||||
							
								
								
									
										37
									
								
								examples/luau/queries/wildcards.luau
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								examples/luau/queries/wildcards.luau
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,37 @@
 | 
				
			||||||
 | 
					local jecs = require("@jecs")
 | 
				
			||||||
 | 
					local pair = jecs.pair
 | 
				
			||||||
 | 
					local world = jecs.World.new()
 | 
				
			||||||
 | 
					local Name = world:component()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function named(ctr, name)
 | 
				
			||||||
 | 
					    local e = ctr(world)
 | 
				
			||||||
 | 
					    world:set(e, Name, name)
 | 
				
			||||||
 | 
					    return e
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					local function name(e)
 | 
				
			||||||
 | 
					    return world:get(e, Name)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local Eats = world:component()
 | 
				
			||||||
 | 
					local Apples = named(world.entity, "Apples")
 | 
				
			||||||
 | 
					local Oranges = named(world.entity, "Oranges")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local bob = named(world.entity, "Bob")
 | 
				
			||||||
 | 
					world:set(bob, pair(Eats, Apples), 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local alice = named(world.entity, "Alice")
 | 
				
			||||||
 | 
					world:set(alice, pair(Eats, Oranges), 5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Aliasing the wildcard to symbols improves readability and ease of writing
 | 
				
			||||||
 | 
					local __ = jecs.Wildcard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Create a query that matches edible components
 | 
				
			||||||
 | 
					for entity, amount in world:query(pair(Eats, __)) do
 | 
				
			||||||
 | 
					    -- Iterate the query
 | 
				
			||||||
 | 
					    local food = world:target(entity, Eats)
 | 
				
			||||||
 | 
					    print(`{name(entity)} eats {amount} {name(food)}`)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Output:
 | 
				
			||||||
 | 
					--  Alice eats 5 Oranges
 | 
				
			||||||
 | 
					--  Bob eats 10 Apples
 | 
				
			||||||
							
								
								
									
										152
									
								
								test/btree.luau
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								test/btree.luau
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,152 @@
 | 
				
			||||||
 | 
					-- original author @centauri
 | 
				
			||||||
 | 
					local bt
 | 
				
			||||||
 | 
					do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local FAILURE = 0
 | 
				
			||||||
 | 
					    local SUCCESS = 1
 | 
				
			||||||
 | 
					    local RUNNING = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function SEQUENCE(nodes)
 | 
				
			||||||
 | 
					        return function(...)
 | 
				
			||||||
 | 
					            for _, node in nodes do
 | 
				
			||||||
 | 
					                local status = node(...)
 | 
				
			||||||
 | 
					                if status == FAILURE or status == RUNNING then
 | 
				
			||||||
 | 
					                    return status
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					            return SUCCESS
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    local function FALLBACK(nodes)
 | 
				
			||||||
 | 
					        return function(...)
 | 
				
			||||||
 | 
					            for _, node in nodes do
 | 
				
			||||||
 | 
					                local status = node(...)
 | 
				
			||||||
 | 
					                if status == SUCCESS or status == RUNNING then
 | 
				
			||||||
 | 
					                    return status
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					            return FAILURE
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    bt = {
 | 
				
			||||||
 | 
					        SEQUENCE = SEQUENCE,
 | 
				
			||||||
 | 
					        FALLBACK = FALLBACK,
 | 
				
			||||||
 | 
					        RUNNING = RUNNING,
 | 
				
			||||||
 | 
					        SUCCESS = SUCCESS,
 | 
				
			||||||
 | 
					        FAILURE = FAILURE,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local SEQUENCE, FALLBACK = bt.SEQUENCE, bt.FALLBACK
 | 
				
			||||||
 | 
					local RUNNING, SUCCESS, FAILURE = bt.FAILURE, bt.SUCCESS, bt.FAILURE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local btree = FALLBACK {
 | 
				
			||||||
 | 
					    SEQUENCE {
 | 
				
			||||||
 | 
					        function()
 | 
				
			||||||
 | 
					            return 1
 | 
				
			||||||
 | 
					        end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function()
 | 
				
			||||||
 | 
					            return 0
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    SEQUENCE {
 | 
				
			||||||
 | 
					        function()
 | 
				
			||||||
 | 
					            print(3)
 | 
				
			||||||
 | 
					            local start = os.clock()
 | 
				
			||||||
 | 
					            local now = os.clock()
 | 
				
			||||||
 | 
					            while os.clock() - now < 4 do
 | 
				
			||||||
 | 
					                print("yielding")
 | 
				
			||||||
 | 
					                coroutine.yield()
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					            return 0
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    function()
 | 
				
			||||||
 | 
					        return 1
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function wait(seconds)
 | 
				
			||||||
 | 
					    local start = os.clock()
 | 
				
			||||||
 | 
					    while os.clock() - start < seconds do end
 | 
				
			||||||
 | 
					    return os.clock() - start
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function panic(str)
 | 
				
			||||||
 | 
					    -- We don't want to interrupt the loop when we error
 | 
				
			||||||
 | 
					    coroutine.resume(coroutine.create(function() error(str) end))
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local jecs = require("@jecs")
 | 
				
			||||||
 | 
					local world = jecs.World.new()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function Scheduler(world, ...)
 | 
				
			||||||
 | 
					    local systems = { ... }
 | 
				
			||||||
 | 
					    local systemsNames = {}
 | 
				
			||||||
 | 
					    local N = #systems
 | 
				
			||||||
 | 
					    local system
 | 
				
			||||||
 | 
					    local dt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for i, module in systems do
 | 
				
			||||||
 | 
					        local sys = if typeof(module) == "function" then module else require(module)
 | 
				
			||||||
 | 
					        systems[i] = sys
 | 
				
			||||||
 | 
					        local file, line = debug.info(2, "sl")
 | 
				
			||||||
 | 
					        systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}`
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function run()
 | 
				
			||||||
 | 
					        local name = systemsNames[system]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        --debug.profilebegin(name)
 | 
				
			||||||
 | 
					        --debug.setmemorycategory(name)
 | 
				
			||||||
 | 
					        system(world, dt)
 | 
				
			||||||
 | 
					        --debug.profileend()
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local function loop(sinceLastFrame)
 | 
				
			||||||
 | 
					        --debug.profilebegin("loop()")
 | 
				
			||||||
 | 
					        local start = os.clock()
 | 
				
			||||||
 | 
					        for i = N, 1, -1 do
 | 
				
			||||||
 | 
					            system = systems[i]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            dt = sinceLastFrame
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            local didNotYield, why = xpcall(function()
 | 
				
			||||||
 | 
					                for _ in run do end
 | 
				
			||||||
 | 
					            end, debug.traceback)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if didNotYield then
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if string.find(why, "thread is not yieldable") then
 | 
				
			||||||
 | 
									N -= 1
 | 
				
			||||||
 | 
									local name = table.remove(systems, i)
 | 
				
			||||||
 | 
									panic("Not allowed to yield in the systems."
 | 
				
			||||||
 | 
					    				.. "\n"
 | 
				
			||||||
 | 
					    				.. `System: {name} has been ejected`
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								    panic(why)
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        --debug.profileend()
 | 
				
			||||||
 | 
					        --debug.resetmemorycategory()
 | 
				
			||||||
 | 
					        return os.clock() - start
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return loop
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local co = coroutine.create(btree)
 | 
				
			||||||
 | 
					local function ai(world, dt)
 | 
				
			||||||
 | 
					    coroutine.resume(co)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local loop = Scheduler(world, ai)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					while wait(0.2) do
 | 
				
			||||||
 | 
					    print("frame time: ", loop(0.2))
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
		Loading…
	
		Reference in a new issue