From 12c86ca679ec50687a9fae7eb26e9570f578b93d Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 16 Jan 2026 07:24:38 +0700 Subject: [PATCH] update README --- README.md | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/README.md b/README.md index 553c4ef..a7204f9 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ - [Shared Components](#shared-components) - [Fragment Requirements](#fragment-requirements) - [Destruction Policies](#destruction-policies) + - [Custom Component Storages](#custom-component-storages) - [Cheat Sheet](#cheat-sheet) - [Aliases](#aliases) - [Predefs](#predefs) @@ -1154,6 +1155,145 @@ evolved.destroy(world) assert(not evolved.alive(entity)) ``` +#### Custom Component Storages + +In some cases, you might want custom storages for fragment components. For example, you might want to store components in a specialized way for performance reasons. The library provides two fragment traits for this purpose: [`evolved.REALLOC`](#evolvedrealloc) and [`evolved.COMPMOVE`](#evolvedcompmove). + +The [`evolved.REALLOC`](#evolvedrealloc) trait expects a function that is called when the fragment storage needs to be reallocated. The [`evolved.COMPMOVE`](#evolvedcompmove) trait expects a function that is called when components need to be moved from one storage to another. + +A canonical example of using custom storages is implementing a fragment that stores components in an FFI-backed storage for better processing performance in LuaJIT. This is an advanced topic and requires a good understanding of LuaJIT FFI and memory management. So I won't cover it here in detail, but here is a simple example to give you an idea of how it works. More information can be found on the [LuaJIT](https://luajit.org/ext_ffi.html) website. + +```lua +local ffi = require 'ffi' +local evolved = require 'evolved' + +-- +-- +-- Define FFI double storage realloc and compmove functions +-- +-- + +local FFI_DOUBLE_TYPEOF = ffi.typeof('double') +local FFI_DOUBLE_SIZEOF = ffi.sizeof(FFI_DOUBLE_TYPEOF) +local FFI_DOUBLE_STORAGE_TYPEOF = ffi.typeof('double[?]') + +---@param src ffi.cdata*? +---@param src_size integer +---@param dst_size integer +---@return ffi.cdata*? +local function FFI_DOUBLE_STORAGE_REALLOC(src, src_size, dst_size) + if dst_size == 0 then + -- freeing the src storage, just let the GC handle it + return + end + + -- to support 1-based indexing, allocate one extra element + local dst = ffi.new(FFI_DOUBLE_STORAGE_TYPEOF, dst_size + 1) + + if src and src_size > 0 then + -- handle both expanding and shrinking + local min_size = math.min(src_size, dst_size) + ffi.copy(dst + 1, src + 1, min_size * FFI_DOUBLE_SIZEOF) + end + + return dst +end + +---@param src ffi.cdata* +---@param f integer +---@param e integer +---@param t integer +---@param dst ffi.cdata* +local function FFI_DOUBLE_STORAGE_COMPMOVE(src, f, e, t, dst) + ffi.copy(dst + t, src + f, (e - f + 1) * FFI_DOUBLE_SIZEOF) +end + +-- +-- +-- Define fragments with our custom FFI storages +-- +-- + +local POSITION_X = evolved.builder() + :default(0) + :realloc(FFI_DOUBLE_STORAGE_REALLOC) + :compmove(FFI_DOUBLE_STORAGE_COMPMOVE) + :build() + +local POSITION_Y = evolved.builder() + :default(0) + :realloc(FFI_DOUBLE_STORAGE_REALLOC) + :compmove(FFI_DOUBLE_STORAGE_COMPMOVE) + :build() + +local VELOCITY_X = evolved.builder() + :default(0) + :realloc(FFI_DOUBLE_STORAGE_REALLOC) + :compmove(FFI_DOUBLE_STORAGE_COMPMOVE) + :build() + +local VELOCITY_Y = evolved.builder() + :default(0) + :realloc(FFI_DOUBLE_STORAGE_REALLOC) + :compmove(FFI_DOUBLE_STORAGE_COMPMOVE) + :build() + +-- +-- +-- Define a movement system that uses these components +-- +-- + +local MOVEMENT_SYSTEM = evolved.builder() + :include(POSITION_X, POSITION_Y) + :include(VELOCITY_X, VELOCITY_Y) + :execute(function(chunk, entity_list, entity_count, delta_time) + local position_xs, position_ys = chunk:components(POSITION_X, POSITION_Y) + local velocity_xs, velocity_ys = chunk:components(VELOCITY_X, VELOCITY_Y) + + for i = 1, entity_count do + local px, py = position_xs[i], position_ys[i] + local vx, vy = velocity_xs[i], velocity_ys[i] + + px = px + vx * delta_time + py = py + vy * delta_time + + position_xs[i], position_ys[i] = px, py + end + end):build() + +-- +-- +-- Spawn some entities with these components +-- +-- + +do + local entity_list, entity_count = evolved.builder() + :set(POSITION_X) + :set(POSITION_Y) + :set(VELOCITY_X) + :set(VELOCITY_Y) + :multi_build(10000) + + for i = 1, entity_count do + local entity = entity_list[i] + evolved.set(entity, POSITION_X, math.random(0, 640)) + evolved.set(entity, POSITION_Y, math.random(0, 480)) + evolved.set(entity, VELOCITY_X, math.random(-100, 100)) + evolved.set(entity, VELOCITY_Y, math.random(-100, 100)) + end +end + +-- +-- +-- Process the movement system with a delta time payload +-- +-- + +evolved.process_with(MOVEMENT_SYSTEM, 0.016) +``` + ## Cheat Sheet ### Aliases