Hey guys, I'm using an ECS(-ish) setup for my game engine. Currently I have 
"entities" which are integers, "components" which are plain old data types, and 
"systems" which are modules containing callback functions.

What I want to do is refactor my game engine so each system module can be 
compiled into its own DLL, and be loaded dynamically at runtime. On first think 
it seems like I just need to figure out how to expose the callback functions in 
the DLL to the rest of the engine, and use shared engine resources in the DLL.

A typical system module:
    
    
    systemOf kinematics: (DynamicBody, Transform)
    
    proc onEngage(entity: Entity) {.kinematics.} =
      # Do stuff when a new entity with DynamicBody and Transform components 
spawn
    
    proc onUpdate(context: Context) {.kinematics.} =
      var id: Entity
      if engines.physics.checkUpdate(id):
        # Synchronize with the shared physics engine
    
    proc onMutate(entity: Entity, transform: Transform) {.kinematics.} =
      # Update DynamicBody based on Transform updates
    
    proc onMutate(entity: Entity, transform: DynamicBody) {.kinematics.} = # 
<-- overload
      # Update Transform based on DynamicBody updates
    
    
    Run

What I plan to do is somehow generate an exported function in every system that 
returns a struct of function pointers that point to the functions in the module:
    
    
    type SystemCallbacks = object
      onEngage: proc(entity: Entity)
      onUpdate: proc(context: Context)
      onMutate: array[2, pointer] # huh?
    
    proc kinematicsCallbacks*(): SystemCallbacks {.stdcall exportc dynlib.} =
      # return a struct of function pointers
    
    
    Run

As you can see from the above, I am unsure about how to deal with function 
overloads, short of completely getting rid of them.

Currently I generate integer IDs for component types so I can do stuff with 
them at runtime:
    
    
    type TypeId* = uint16
    
    proc getId(): TypeId {.noinit.} =
      var id {.global.} : TypeId = 0
      result = id
      id += 1
    
    proc typeId*(T: type): TypeId {.noinit.} =
      # generates a unique ID per instantation of the implicit generic
      # source: https://stackoverflow.com/a/2486090
      let id {.global.} = getId()
      result = id
    
    
    Run

So I thought maybe I can use a sparse array indexed by `TypeId` to resolve 
overloads:
    
    
    type SystemCallbacks = object
      ...
      onMutate: array[0..TypeId.high, pointer] # hmm
    
    
    Run

But therein lies the problem: how do I generate a consistent `TypeId` that can 
be used in multiple DLLs?

A solution I thought of is to create a separate module where I register 
component types:
    
    
    # componentregistrar.nim
    
    import "./components/dynamicbody"
    import "./components/transform"
    
    # creates exported functions that return `TypeId`
    
    register(DynamicBody)
    register(Transform)
    
    
    Run

and compile _that_ into yet another DLL, and have the engine and system DLLs 
use the IDs from that DLL. It's an extra step though, and adds some complexity. 
Is there a better way? 

Reply via email to