Help with type trickery for implementing an Entity Component System framework

I’m trying to write a type-safe Entity Component System in OCaml. The idea is that an entity is has values for a set of components, and type of an entity is parameterised by the components it has so that it is only well-typed to get the value for a component/entity if the component is in the entity’s type.

I tried using object types for this but couldn’t get it to work:

type 'a entity constraint 'a = < .. >

module type Component = sig
  type field

  type t
end

type ('field, 'a) component =
  (module Component with type field = 'field and type t = 'a)
  constraint 'field = < .. >

val empty : < > entity

val get : 'field entity -> ('field, 'a) component -> 'a

val set : (< .. > as 'a) entity -> ('field, _) component -> < 'field ; 'a > t

Is it possible to fix this? If not, any suggestions for doing this kind of thing in a different way?

Not sure about the exact problem here but I can’t not mention this (note
the date, it predates the current hype for ECS):

2 Likes

Hi roddy!

I’m the author of the blog-post cited by @c-cube … and the approach taken there might be different from what you’re after.

The next blogpost: Cranial Burnout: A Component-based GUI with Functional Flavour has an updated note thanks to Leo White suggesting looking at “mixins”. Unfortunately, I see the link in the post is stale… instead look here: Classes - Real World OCaml, and the relevant material starts under the heading “Virtual Classes and Methods”, eventually getting the subheading “Mixins”.

Comparing the two approaches, mixins and my implementation of “components”:

They both offer a means of composing properties… and behavior, though with mixins behavior is more direct, whereas components imply behavior via properties.

One major difference is that mixins offer static guarantees and language-level support, whereas the components are dynamic (suited to additions and removals at runtime).

Also, the components are naturally grouped by property, and suited to operating by-property. The mixin objects could of-course be grouped similarly, but there’s no requirement (or natural inclination).

I once worked at a company where another team (on a different game) was using a mixin approach, while we were using components similar to what I posted about (though in C++ in both cases).

Anyway, their game-objects were predefined classes built of mixins. The player inherited from twenty-or-more mixins, like Controllable, Targetable, etc.

Again, the difference was that they had compile-time guarantees, and quick access to properties/features from a game-object.

What we had was more flexible at runtime, and imposed a property-wise style of processing rather than traditional object-wise.

In this specific situation, processing by-property turned out detrimental (good postmortem material here!): primarily because the team overall was still familiar with OO-style, typical game-code like object.Update(). However, property-wise updates can be very helpful: It can be compatible with data-oriented design, systems processing an array (table) of data, which many do for perfomance (new Unity ECS, as opposed to old Unity OO-ish components). It also simplifies multithreading and avoids weird update-order bugs (which are usually ignored-with-hope-nothing-is-serious, but would require double-buffered game-state to actually update correctly – whereas any specific property-table sensitive to update-order can easily be double-buffered where required).

I tried to think of a way to satisfy the interface you propose, but the type-theory part of my brain is pretty weak. But one limitation would be that you couldn’t really have a function which conditionally adds a component at runtime, right? What would the return type be? Schrödinger object? :slight_smile:

But you could use the mixin approach to flexibly compose (predefined) entity types… and perhaps also satisfy a component-like interface, though I’m not sure it’s of value in this case – might be!

It’s nice to see someone exploring this space in OCaml. Is this game-related?

2 Likes

Oh hi, fascinating you’re there. Do you happen to have newer insights
about ECS and the likes in OCaml? Or even structure-of-arrays using
bigarrays or similar performance tricks?

I never did get far enough along to deal with optimizations. Though without serious constraints on what a component can be (eg. BigArray element types), or some new developments with unboxed values in OCaml… I wouldn’t expect this could be very ideal. You probably have more ideas than I do – my understanding of OCaml is quite surface-level, I haven’t peeked under the hood.

I’ve recently started experimenting with Zig, with a eye to using it for compact and precise memory-layout. Then interfacing with OCaml, but the incentivizing problem doesn’t need access to the “element” datatypes from OCaml. Ultimately, this is a workaround which will be leveraging Ctypes for OCaml’s view of things. I don’t know what might be involved in getting OCaml to work with “packed structures”… but some of the unboxed proposals I’ve seen in this direction sound very interesting!

Sounds cool, I also keep an eye on zig (haven’t written any yet, but I lurk on their IRC channel). I think in many cases using an ECS should also simplify the FFI between C/Zig/rust and OCaml, since it’s mostly about passing integer indices and scalar types around, am I correct? :slight_smile:

A performance-oriented ECS does stress simple data in contiguous arrays, associated by ID – essentially SoA. This could probably be a great fit as you’re suggesting: implemented in C/Zig/Rust while higher-level OCaml code could be presented views of this via Bigarrays or individual values.

I was going to write about how restricting things to Bigarray or simple values would be a pretty harsh constraint. Especially considering things like external Physics libraries or custom non-component-based systems like AI perhaps… which have larger and complex structure. But with these examples, the Physics would need an FFI anyway, and the AI “component” would amount to an array of OCaml values.

So, maybe a performance-oriented ECS out there is already suited to hooking into via FFI…?

Myself, I generally prioritize flexibility, with performance as a “nice perk”, and taking comfort in the options to optimize specific components if needed. So some components are larger records, and some even contain closures… For me, the value is in the free-form association of data to an entity and then iterating/mapping over combinations of that data.

I don’t expect I’ll be exploring options for a high-performance ECS. Only considering ways to improve performance without adding (too many) constraints. Some people view ECS as primarily for performance, and make concessions to achieve it. I didn’t seek components/ECS as a solution to improving cache-coherency, but as a way of representing complex entities and operating on them based on their properties rather than a singular classification.

The problem which led me to finally give Zig a try is an editable representation of geometric models – in OCaml I had a lot of indices making code uglier than pointers would be. On top of that it felt wrong that such less-than-pleasing code would consume extra memory (boxing) and be inefficient. I realized that I could have a very nice and narrow interface from OCaml to some primitives implemented in something C-like, so decided this was a good opportunity to try Zig. Surprisingly, Zig offers a lot of compiler guarantees – and therefore complaints. :slight_smile: More difficult than it would have been in C… plus learning. Neat language for sure, though.

2 Likes