Ghost
Ghost

Reputation: 173

Is it possible to base a language around the Entity-Component-System architecture?

I've been toying with the idea of creating an ECS based programming language in my head for a couple of weeks now. However, I'm not quite sure creating a general-purpose language based on ECS is possible.

How would data structures be implemented? How could you do graphics stuff or network stuff?

I'm starting to think it isn't possible, but I decided to ask here before abandoning the idea. I really want to make this work but I just don't know if it's possible.

So, is it possible? How would you do the things I mentioned?

Thanks in advance!

Upvotes: 5

Views: 1548

Answers (2)

thebugger
thebugger

Reputation: 185

I have been having the same idea for a couple of years now. I think it is possible, but I also think ECS alone would not be enough. Unfortunately ECS is the only widely agreed and well defined thing we currently have, but more things around it must be built in order for an "ECS based language" to be useful. And by useful I mean more general purpose maybe, and easier to use. For example: Events (very useful to communicate across systems), Resources (basically singletons), States (to sync) etc.

Data structures can be implemented inside the ECS formalism: just consider every time you have a pointer, you would then have an Entity. For example, a linked list will be a chain of entities. Every entity would have a component which is the Node, and another component which is the Next containing one field which is the Entity to the next Node. Being components implicitly optional, the last entity in the linked list chain will not have the Next component:

Entity -> Node{ /* content here */ }, Next{ Entity }

You can extend this to any other data structure. I am not saying this is ergonomic or easy to work with, just that it is possible on paper.

Upvotes: 0

user14369196
user14369196

Reputation:

We actually have a proprietary one we use in our case. It's far from a full-featured language and just a DSEL designed to make working with ECS data structures and components easier.

The main focus for us was actually compile-time reflection for things like automatic serialization of any data type defined in the language (UDTs are defined using an identical struct interface in C with identical member alignment) along with allowing any data type defined in the language to be used immediately as a component type in a scene without any explicit registration of the data type (each data type gets its own unique type index which can be grabbed at compile-time easily without RTTI). Mostly we found that doing things like serialization and type registration and, most of all, reflection in languages like C and C++ tend to require enormous amounts of boilerplate or extremely fancy template metaprogramming. We started to get tired of it all, so we made a fairly simple C-like language (and the compiler actually just generates C code) that automates it all for us. Actually, we would cease to benefit from it so much once C++ implements reflexpr from Reflection TS.

The language is actually focused predominantly on defining data types in a way compatible with C, but automatically generating functions to serialize, deserialize, compare, swap, and register them with the ECS. It's straightforward there to do things like iterate over all the member variables of a struct at compile-time and serialize them, swap them, etc. We still end up implementing most of our logic in C++ and just pass the data through our language to C++. For example, we can just write:

struct Foo
{
    float x, y, z;
};

... and immediately insert Foo components into our ECS scene without having to write like, ecs.register<Foo>(); or anything of this nature. I never found that type registration to be such a huge deal since most engines don't have more than a few dozen component types, but serialization and deserialization in particular along with generating GUIs in our editor was a big PITA. The language's focus was primarily to make that a breeze although it does revolve around the ECS as the central way of storing and representing all the program data. It allows us to do things like:

def(generic_type) void serialize(out_stream& out, generic_type udt)
{
    // Compile-time reflection: the key feature of our language and the
    // main rationale for its existence. Calls serialize(out, member) for
    // each member of the user-defined type/struct.
    $for_each_member(udt, serialize, out);
}
def void serialize(out_stream& out, float val)
{
    out.write_float(val);
}

We also have some limited support for generics as can be seen above along with function overloading (we don't actually have member functions but writing x.some_func(y) is just a syntactical alternative for writing some_func(x, y) -- we didn't bother with encapsulation and object-oriented programming since the primary focus is ECS).

As for the ECS data structures, they're not different than how we'd implement them in languages like C or C++, and actually, they are implemented in C++. The first thing we did with our compiler was to make sure it can easily interop with C, and call C API functions so that we could implement whatever we need on the C end (including the fundamental ECS data structures and also things like graphical functions). That's fairly standard stuff though when implementing a new language and compiler/interpreter is to have it interop with a language like C through some uniform API or foreign function interface. In our case, since our compiler generates C code rather than machine code, it was very straightforward to allow it to call any C functions (including ones implemented in C++) we like exported through a dylib immediately.

Upvotes: 4

Related Questions