Re: Architectural Discussion: Implementing ECS and Data-Oriented Programming (DOP)
- Posted by euphoric (admin) 3 weeks ago
- 275 views
Interesting thought experiment, but having gone down similar rabbit holes with Euphoria game loops and state engines back in the 4.0 beta days, I'll give you the ground truth: std/map.e will hit a wall fast if you use it like this in a high-churn loop, but you can do DOP if you play to Euphoria's actual strengths.
Here is the breakdown on your questions:
1. The Memory & Handle Trap (std/map.e)
You hit the nail on the head in question #1. std/map.e is not a first-class language type; it's a wrapper around an internal lookup table that assigns an integer handle.
If you create nested maps for every component on every entity:
Euphoria's native garbage collector will not clean them up.
If an entity dies, you can't just set world[id] = 0 and walk away. You have to write an explicit cascade: delete the entity map, delete every component map inside it, and reclaim the ID.
Forget a single map:delete(), and you have a classic C-style memory leak in a high-level interpreted language.
If you want automatic memory management and DOP, plain old nested sequences are infinitely safer because reference counting handles cleanup the millisecond an entity drops out of scope.
2 & 3. Performance: The Hash Penalty vs. Copy-on-Write
In C++, ECS is about CPU cache lines. In Euphoria, there are no cache lines-everything is an object pointer under the hood.
std/map.e uses hashing and bucket lookups. In a tight loop running at 60 FPS (or chewing through 10,000 records in a batch):
-- Doing 4 hash lookups per entity per frame: atom new_x = map:get(p, "x") + (map:get(v, "dx") * dt) The overhead of hashing string keys ("x", "dx") inside the loop is brutal.
The Euphoria Alternative: Enum/Constant Offsets on Flat Sequences If you want DOP speed without the OO nonsense, the idiomatic Euphoria way has always been flat parallel sequences or 1D arrays with enum offsets, not dynamic string maps:
-- Component indices (Zero hash overhead) enum POS_X, POS_Y enum VEL_DX, VEL_DY -- Dense flat stores (indexed directly by Entity ID) sequence pos = repeat({0.0, 0.0}, MAX_ENTITIES) sequence vel = repeat({0.0, 0.0}, MAX_ENTITIES) -- Update loop (Direct index, blistering fast): for id = 1 to active_count do pos[id][POS_X] += vel[id][VEL_DX] * dt pos[id][POS_Y] += vel[id][VEL_DY] * dt end for
Because pos[id] is mutated in-place and Euphoria's copy-on-write optimizer recognizes single-reference sequences, it will mutate directly in memory without cloning the entire tree, provided you don't assign temporary variables to slices right before the write.
4. Schema Validation via type
Custom types are awesome in Euphoria, but putting map:has() checks inside a type definition will destroy your frame rate if without type_check isn't enabled in release mode:
type PositionMap(map:map m) return map:has(m, "x") and map:has(m, "y") end type
If you pass that into a procedure, Euphoria validates it on every call. Fine for debugging, but in production, you definitely want without type_check at the top of your system files, or just validate once at the factory boundary when the entity is spawned.
The Verdict
If you're writing a business app, database processor, or UI state manager where updates happen on user events (milliseconds don't matter), passing std/map.e handles around is fine and decoupled.
If you are writing an actual game or real-time simulation, ditch std/map.e for your core loop. Use integer IDs to index flat, pre-allocated sequence pools with enum constants. You get 90% of the DOP architectural cleanliness, full native garbage collection, and it will run roughly 50x faster.
Good to see someone still pushing architectural patterns here, though! What are you building that needs this?

