Skip to content

Instantly share code, notes, and snippets.

@bytejon
Created March 24, 2025 13:08
Show Gist options
  • Save bytejon/6e9abca901948dc6c9421a0a8744b592 to your computer and use it in GitHub Desktop.
Save bytejon/6e9abca901948dc6c9421a0a8744b592 to your computer and use it in GitHub Desktop.
--!strict
-- jonbyte, 2025
-- types
type Int = number
type i53 = Int
type Type = Int
type Index = Int
type Map<K, V> = { [K]: V }
type Array<V> = Map<Int, V>
-- data
local types = {} :: Array<{
read indexMap: Map<Index, Int>, -- sparse set
read entities: Array<i53>, -- dense set
next: Int, -- count of alive entities [0, max + 1]
max: Index -- highest allocated index
}>
-- functions
local function assertType(
type: Int
)
if not types[type] then
error('invalid type')
end
return types[type]
end
local function getIndex(
entity: i53
)
return (entity // 2^5) % 2^32
end
local function decode(
entity: i53
)
-- LO [type(5b)][index(32b)][version(16b)] HI
local type = entity % 2^5
local version = entity // 2^37
return type, getIndex(entity), version
end
local function encode(
type: Type,
index: Index,
version: Int
)
return (version * 2^37) + (index * 2^5) + type
end
local function createType()
table.insert(types, {
indexMap = {},
entities = {},
next = 0,
max = -1
})
return #types
end
local function createEntity(
type: Type
)
local scope = assertType(type)
local indexMap = scope.indexMap
local entities = scope.entities
local entity: i53
if scope.next <= scope.max then
scope.next += 1
entity = entities[scope.next]
local index = getIndex(entity)
indexMap[index] = scope.next
else
scope.max += 1
scope.next += 1
local index = scope.max
entity = encode(type, index, 0)
indexMap[index] = scope.next
table.insert(entities, entity)
end
return entity
end
local function exists(
entity: i53
)
local type, index, version = decode(entity)
local scope = assertType(type)
local entities = scope.entities
local pos = scope.indexMap[index]
if not pos then
--[[
1. the entity was never created
2. the entity was destroyed
3. the index is invalid
]]
return false
end
if version ~= entities[pos] // 2^37 then
-- the entity index version is stale
return false
end
return true
end
local function destroy(
entity: i53
)
if not exists(entity) then
return
end
local type, index, version = decode(entity)
local scope = types[type]
local entities = scope.entities
local pos = scope.indexMap[index] :: number
local next = scope.next
if pos ~= next then
scope.indexMap[getIndex(entities[next])] = pos
entities[pos], entities[next] = entities[next], entities[pos]
end
entities[scope.next] += 2^37 -- increment version
scope.indexMap[index] = nil
scope.next -= 1
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment