Hello, I was wondering if it would be possible to do something like this in Roc (C# code):
class ComponentRegistry
{
Dictionary<Type, Object> components = new Dictionary<Type, object>();
public void AddComponent<T>(T component)
{
components[typeof(T)] = component;
}
public bool GetComponentOfType<T>(out T result)
{
if (components.ContainsKey(typeof(T)))
{
result = (T)components[typeof(T)];
return true;
}
result = default(T);
return false;
}
}
Basically, have a generic store of components that would let you retrieve a component by its type? In case context helps I'm brainstorming how ECS could be done in Roc
I believe tags are the recommended mechanism for "runtime typing"
But tags require the ComponentRegistry to know about every possible thing that can be put into it?
we don't have this kind of reflection. You can see here how a type is turned into a value typeof(T)
. That's not something we can do
types and values live in distinct universes in roc
interestingly, to get this sort of feature you can be both more strongly and more weakly typed
e.g. haskell can actually achieve the same result (if you enable a bunch of language extensions)
If you don't want the component registry to know every "type" it can use as a dict key, then it sounds like you'll want to use strings as keys. Perhaps a component is a record with a .type
string field.
That wouldn't work in roc. What would the actual component be? { type: Str, component: ??? }
So it would have to be an explicit tag I think.
If you used a type variable for the component, all components would need the same type.
As a side note, I think that the tagged version would be cheaper at runtime then the type reflection version.
But yeah, not flexible due to no reflection.
I don't know ECS very well, so this is just an idea:
MyECS.Component : { type: Str }*
MyECS.ComponentRegistry : SomeDictLibrary.Dict
MyECS.addComponent : MyECS.ComponentRegistry, MyECS.Component -> MyECS.ComponentRegistry
registry : MyECS.ComponentRegistry
registry = MyECS.initializeComponentRegistry
registry = MyECS.addComponent registry { type: "friend", name: "Friendo" }
Hmmm...I'm actually really interested, does that work in roc? At a minimum, it would break if two component have a field with the same name but different types, but otherwise it may work.
MyECS.Component : { type: Str }*
does not work. That *
type variable needs to occur on the left-hand side
Oh, whoops, lemme edit
MyECS.Component a : { type: Str }a
MyECS.ComponentRegistry : SomeDictLibrary.Dict
MyECS.addComponent : MyECS.ComponentRegistry, MyECS.Component -> MyECS.ComponentRegistry
registry : MyECS.ComponentRegistry
registry = MyECS.initializeComponentRegistry
Friend : { name: Str }
friendo : MyECS.Component Friend
friendo = { type: "friend_idk_this_aspect_is_pain", name: "Friendo" }
registry = MyECS.addComponent registry
None of those type aliases are necessary, they're just to show what types things would have (and to skip writing full definitions)
It turns out my understanding of ECS is just OOP with methods extracted into systems (not shown here) :laughing:
I think the .Dict
also needs some type parameters right? and I think that makes this fall apart
what you want is some sort of way to "forget" about that type variable
and we can't do that. There are ways in haskell with a forall
, or in rust with the Any
trait
I imagine this particular ECS implementation approach isn't the most Roc-y regardless
or dyn Trait
in general
String encoding? :nauseated:
void *
solution to all our problems
I don't understand and I'm terrified
So what's the real solution here? It's a legitimate use case, so we should have a clean pattern ready.
A tag and explicitly naming the possible types is definitely the cleanest in current roc. The library would just expose it as a type variable and the user would fill in the tag
Ooh
ComponentRegistry a : List a
# not sure if the original deduplicates based on the type?
addComponent = List.push
findComponent : ComponentRegistry a, (a -> Bool) -> [ Found a, NotFound ]
(wow, Roc's type variables and type inference are a powerful combo for terse yet readable code, IMO)
So does this or something like it work in general?
dict : Dict Str { }a
dict = Dict.Empty
|> Dict.insert "Friend" { hp: 12, name: "Foo" }
|> Dict.insert "Enemy" { hp: 10, atk: 7 }
dict a : ...
?
no collections are homogeneous
Ok. That is what I thought.
@Jared Cone with the original code, wouldn't that limit to exactly one component of each type? Is that normal for ECS. This just doesn't quite match my understanding of it.
Yeah I think this use case needs variable component types/bodies
I think the a
in Folkert's example would be a tag union with "component types" as tags and "component data" as tag payloads
with the original code, wouldn't that limit to exactly one component of each type? Is that normal for ECS. This just doesn't quite match my understanding of it.
Yes, I was just wanting to keep the example simple. The core issue is the same regardless if it's exactly one component or more than one component of each type.
So a basic design in Roc might be something like:
EntityId : U32
Game : {
deltaSeconds : F32,
velocityComponents : Dict EntityId Vector3,
gravityComponents : Dict EntityId Vector3,
...
}
GravitySystem : Game -> Game
GravitySystem = \game ->
velocityComponents = ... apply gravity to each entity that has velocity and gravity components ...
{ game & velocityComponents }
Issues:
I thought one of the goals of ECS pattern is to eliminate bloated "God"/"Game" objects, so maybe mimicking the implementation from another language is breaking the spirit of the pattern?
The idea is a System can say "Give me the entities that have X and Y components so I can do some work with them". As far as I can tell in Roc currently the only way to do that is with a "God" object.
So I think this would be a functional example that would theoretically perform similar to a regular ECS:
Type : [
Animal,
Plant,
Player,
Chest,
]
Entity := { id: U64, type: Type }
ComponentProperties : {
hasPosition: Bool,
hasVelocity: Bool,
hasHealth: Bool,
hasInventory: Bool,
}
supportedComponents : Type -> ComponentProperties
supportedComponents = \type ->
when type is
Animal ->
{ hasPosition: True, hasVelocity: True, hasHealth: True, hasInventory: False }
Plant ->
{ hasPosition: True, hasVelocity: False, hasHealth: True, hasInventory: False }
Chest ->
{ hasPosition: True, hasVelocity: False, hasHealth: False, hasInventory: True }
Player ->
{ hasPosition: True, hasVelocity: True, hasHealth: True, hasInventory: True }
Item : [ SomeTag ]
Components : {
# These could also be lists with empty data for entities without this data.
position: Dict U64 { x: F64, y: F64, z: F64 },
velocity: Dict U64 { dx: F64, dy: F64, dz: F64 },
health: Dict U64 U64
inventory: Dict U64 (List Item)
}
moveSystem : List Entity, Components -> [ T (List Entity) Components ]
moveSystem = \entities, components ->
List.walk entities (T entities components) (\T currentEntities currentComponents, entity ->
supported = supportedComponents entity.type
if supported.hasPosition && supported.hasVelocity then
{x, y, z} = Dict.get currentComponents.position entity.id
{dx, dy, dz} = Dict.get currentComponents.velocity entity.id
nextPosition = { x: x + dx, y: y + dy, z: z + dz }
nextComponents = {currentComponents & position: Dict.insert currentComponents.position entity.id nextPosition}
T currentEntities nextComponents
else
T currentEntities currentComponents
)
Of course it is custom and would be much harder to make generic in roc. Would have a bunch of open ended type variables that need to be filled in.
As a side note, this might be made more reasonable with abilities. Could have an ability to get an entities position. That ability would be on all entities and would just look up the entity in the position dictionary. If the entity does not support position, it would return None.
But that would also be one ability per component, so it would be quite odd.
would be much harder to make generic in roc
True but in Elm I've always found this to be a good thing. If you're making a game (as opposed to selling a game engine like Unity) then isn't it better it to be explicit rather than generic?
I say this with zero experience in game dev, it's just what I've found in other types of application.
I mean if you are new to game making and want to use someone's basic game platform with some ECS library, you would prefer not to have to learn and implementation everything explicitly cause it is all new to you.
The big issue is that I believe some ECS library basically wouldn't be functional without reflection or something more powerful in those lines. It would basically be an empty shell of type variables that take explicit knowledge to implement. Probably would be best just to make your own custom implementation or ECS rather than use the library.
There are two ECS packages in Elm (which has a very similar type system to Roc) if you're interested in seeing what the API and implementation of those look like. I don't know that much about ECS so I don't know if they are good implementations of it though.
https://package.elm-lang.org/packages/justgook/elm-game-logic/latest/ (source code: https://github.com/justgook/elm-game-logic/tree/3.0.0)
https://package.elm-lang.org/packages/harmboschloo/elm-ecs/latest/ (source code: https://github.com/harmboschloo/elm-ecs/tree/2.0.0)
So elm-game-logic
just ignores the entity typing problem all together by using an array of Maybe
. That might be an acceptable option, but it has a few problems:
Maybe
elm-ecs
is more complicated for me to wrap my head around. Will try to give an analysis once I get what it is actually doing under the hood.
Ok, elm-ecs
looks better. It uses a lot of type variables and indirection (that could theoretically be compiled away) to make this work. Essentially each component has to know how to get/set itself in the world via the use of lambdas. Assuming those lambdas get compiled away it should generate similar code to what I have above. Though it does still have some clear drawbacks from the direct implementation:
Ecs.Components49
so if you have 50 components, you need the author to update the library. Same with a number of the accessors.So I would label this as both having fundamental overhead and restrictions compared to the raw implementation, but still cool and probably useful for many hobby games.
Would this be a job for a Roc codegen/scaffolding tool? Or maybe just copy/paste/customize haha
(with regard to the idea I'm gathering that "Roc ECS developers of all skill levels might be better served by a manual implementation from scratch, since a Roc ECS library can't provide meaningful zero-cost abstractions")
I think copy and modify would work great for something like with. Also would work well to be able to make a declarative list of types and their components and then generate it.
So I was watching a talk on ECS to better understand how it is done with good data oriented design. I think that there may actually be a way to modify something like elm-ecs
to generate almost the exact same thing as a proper DoD entity component system (assuming all lambdas inline). The actual core difference is just expanding an entity include some metadata about the components and tags that is has.
This ECS still doesn't exactly have entity types, but it has entity tags that can be used in the same way. Though it has no proper verification to stop a user from accidentally make an entity that is missing a component that it should have. To get that sort of full verification, you would either need a tag per entity type, or make the entity type something separate.
I should add a warning to the talk: it is a lot of C++ and a metric ton of templates.
To clarify, there shouldn't be static types of entities. An entity is a collection of components and components can be added to or removed from entities throughout their lifetime
Yeah, I didn't realize that at first. see it now with these talks
Yeah, I forgot that too in my examples and treated entities as components
There is fundamental clash between ECS and functional programming.
The fact that any component of an entity can modify other entities and it's components in the game world, eg. on colision with other object, open the door.
Which is bonkers from the perspective of FP.
I believe that we still don't know how to model the world so rich and dynamic with FP and type systems that we've got.
Just blunt copying the pattern doesn't work!
Which is weird, because we are talking about "composability" of FP constructs, and yet, we can't have a blank cotton ball and stick random stuff to it and be sure that our program will run.
With Roc's inplace mutation, I think that can be worked around. Of course, a small mistake can lead to tons of copying.
So definitely a case where you need to tread carefully or you could ruin performance
Maybe this is good place where abilities might help? Or platforms?
The fact that any component of an entity can modify other entities and it's components
Unfortunately ECS has two different meanings. The type of ECS I'm referring to is where the Components are just data, no behavior. Systems are behavior, no data. So in that case Components don't modify anything, but any System can modify any entity or component.
I think FP should be a good fit for ECS because Systems can be implemented as pure functions. The thing that I'm a little roadblocked with is how to do it in Roc since there (currently) isn't a data type that can store "anything" that can later be cast to the correct underlying type. Though... could open tags somehow support this? I haven't worked much with them.
An open tag can't be stored. Has be a concrete type.
I think making an ECS library will be hard in Roc (would require lots of type variables and lambdas like the linked elm libraries)
I think making a concrete ECS where each component gets it's own array or dictionary should be totally doable. Just has to be done manually.
I haven't thought this through at all, and don't know about ECS, but can't you have a general ECS module expressed in terms of a completely open tag system (possibly with tags the ECS module specifically understands), and accept a callback function from the app that takes as input the known app-specific tags? At that point, the compiler would have its concrete definition. This would be a bit like Elm update functions, I presume.
I think you would run into storage issues. For example, I want the position component in one array and the color component in a separate array.
If the user of the library is passing an open tag of type [ Pos F32 F32, Color U8 U8 U8 ]*
. The underlying library would know nothing about the type and would not able able to do anything with it. The underlying library could store them all to one master list, but it would have no way to distinguish between them.
To store Pos
in the position array and Color
in the color array would require the library to know about the individual tags.
Hi! Is there a platform and example of a simple http server in Roc?
Some work was done on this in PR #2975 but it may require some updating.
Thanks. Would be relevant for a large audience if a solid ecosystem exists for building web services on http, like rest, gRPC, graphql, etc.
Same goes for event-driven microservices, an ecosystem for subscribing and publishing to a Kafka or Nats/JetStream event bus would make it easy for service meshes to experiment with a microservice in Roc.
Absolutely, we definitely plan to work on it in the future.
Would make for a fantastic well-scoped domain.
Probably in Rust. With arena memory management with requests and incoming events with their separate arenas.
Last updated: Jul 06 2025 at 12:14 UTC