Arceus.jl — A Lightning-Fast Behavior Resolution System

Hi all,

I’m happy to present Arceus.jl, a fork of a high-performance AliceRoselia’s original package Arceus, which introduces a novel way to resolve behaviors for entities based on their component sets—using magic bitboards, an optimization technique originally used in chess engines for O(1) move lookups.

Arceus.jl provides constant-time behavior resolution for ECS-style setups where entities are encoded as UInt64 bitfields. This is especially useful in games, simulations, or other systems where thousands of entities need to be matched to behaviors in real-time.

Explanations

You define a @traitpool, which is a set of traits (characteristics influencing the entity’s behavior) and subpool (subset of traits and other subpool).
Once done, you define a new instance of a traitpool with @make_traitpool and add the relevant traits, only those who describe your object, you can add and remove traits with @addtrait and @removetrait.
Once done, you create a new lookup function with @lookup, you specify in the body how each traits affects the result when present. This may take a while the first time since it compute the magic number which is slow. But for any future run, you won’t have problems (unless you add or remove some test in the lookup function)
Now you are all set to create your lookup table with @make_lookup.
Now for any instance of that trait pool, the resulting behavior for it’s set of traits will be retrieved in less than 10ns, even on a potato hardware.


Features

  • O(1) Behavior Lookup
    Behaviors are precomputed for all possible component combinations, enabling instant retrieval.

  • Magic Bitboard Caching
    Magic numbers are expensive to compute, so they’re deterministically cached to disk (CSV), enabling fast startup on subsequent runs.

  • ECS Compatibility
    Works with any archetype-based ECS that represents components using bitmasks or similar binary encodings.

  • Fine-Grained Bit Control
    You can allocate custom bit ranges for component pools, set precise trait positions, and manage trait dependencies.


Example

Here’s a simplified usage example:

using Arceus

@traitpool "EndingTrait" begin
    @trait start
    @trait meetX
    @trait gotSaber
    @trait lostAlly at 2
    @subpool side begin
        @trait good
        @trait evil 
    end
end

@make_traitpool GameEnding from "EndingTrait" begin
    @trait start
end

@addtrait GameEnding begin
    @trait meetX
    @trait lostAlly
    @trait side.good
end

f1 = @lookup END "EndingTrait" begin
    val = UInt(0)
    if @hastrait END.start
        val |= 1
    end
    if @hastrait END.meetX
        val |= 2 
    end
    if @hastrait END.gotSaber
        val |= 4
    end
    if @hastrait END.lostAlly
        val |= 8
    end
    if @hastrait END.side.good
        val |= 64
    end
    if @hastrait END.side.evil
        val |= 128
    end
    return val
end

@register_variable f1
ending = @make_lookup f1

println(ending[GameEnding])

This allows sub-10ns behavior lookup on low hardware.


Known Limitations

  • Heavy Initial Computation: Magic numbers are expensive to generate. It’s best to compute them once during initialization—after that, they are cached to a CSV file.
  • 64-bit Limit: The system operates on UInt64, meaning you’re limited to 64 component flags. Beyond that, the magic bitboard trick doesn’t apply.

Links


Contribute

Contributions are very welcome—whether it’s suggestions, PRs, or feedback. And please consider supporting the original author of the system!


Summary

If you’re working with ECS architectures and need lightning-fast behavior resolution for complex trait combinations, Arceus.jl might be worth a look. Let me know what you think, and feel free to ask questions or open issues.

7 Likes

I don’t know if caching performance is important, but caching to an Arrow file (Arrow.jl) may be more performant and no harder than caching to CSV.

1 Like

Thanks.
It may definitely help to cache bigger data easily.

Depending on the structure of the magic number tables, I suspect it may even not be a bad idea to roll your own binary format.

Oh also, where are you putting the cache files?

It will just rest in the same folder as the code. but I will fix that soon. And having some custom binary format may really help. I’m on it.

Nice one! I think you’re nailing an all-around fast ECS infrastructure for Julia!

If you want to continue this line of work, I have additional challenges depending on how far you’re willing to go. There are those I failed to do. If you want to try, good luck.

  1. Add pext support. (Faster than magic lookup on some arch) See. Bit Manipulation Instruction Set - #5 by yuyichao
  2. You seem to be implementing a traditional ECS in addition to this one. One realization about archetypal ECS is that each combination of traits might get used many times in an update. For example, one might update entities on fire with -5 HP and poisoned entities with -5 HP. One should be able to merge them and use the LLVM compiler to efficiently update them both.
  3. If you’re up for more challenges, take a look at something like accelerated linear algebra, reactant, etc, to not only optimize scalar update, but also linear algebraic updates as well.
  4. Dynamically compiling for each archetype introduces a compiled Julia function. These functions currently stay in memory and do not get garbage-collected. This is a known issue. If you’re daring enough, you can fix it. (This problem is a really hard one; you need to fix Julia itself. Maybe keep this later.

See also FunctionWrappers.jl.

As to why you’d need both a bit-lookup-based ECS and an archetype-based ECS, it’s because bit-lookup is only fast with simple precomputed behaviors. If you need to look up a virtual function, it’s starting to get slow.

You do know that coding things in Julia is a challenge right? For example, take a look at this. Comparison tables for various Julia packages

Julia beats everyone else hands-down. If you’re up to the challenge, you’re competing with Unity, Flecs, Bevy, etc. You decide if you want to or not. Good luck.

I’m surprised by your capability. Good luck with whatever you want to do further.

Give me just 3 days and you will be surprised.