Mhhh okay…Still not too convinced here I guess. Let’s wait for that performance benchmark ;D ;D
(Not that it matters too much because Artemis is already pretty fast? So if you want to use an ES you already have a pretty good option there imo)
To repeat myself: Problem dictates data. Data dictates code. If you do the reverse you’re writing business software targeting mid-to-large companies with a faster turn-over of engineers vs. product life-cycle. What you’re not doing is “thinking low level, writing high level” which is needed for system programming.
Or, in 3 words, a solution looking for a problem.
Cas
BAH!! You wouldn’t believe what I’ve seen; beautiful accelerating heuristics dynamically unfolding through time.
Proebsting’s Law: Compiler Advances Double Computing Power Every 18 Years.
Don’t write a system to guess what you know.
Technically what I’ve done is taking stock Java 5 language level features and inverting OOP on its head to make Java as functional like as possible without requiring taking the step of moving to a JVM based language to achieve this level of dynamic behavior. Runs everywhere including all versions of Android. The problem, having a large differentiated OOP SDK to deploy across the Android ecosystem, existed before the solution. It’s nice that this direction also fully supports a fully dynamic CA based ES too without hard locking the API to one particular use case allowing dynamic optimization possibilities. I tried everything OOP (well except dependency injection; shudder / eh! We can at least agree DI reeks and is of the devil right?) under the sun to ease the pain before finding something that significantly eased the pain and brought joy to coding again. You too will have your pain eased in time… ES4L! :point: ES 4 Life, Yo… 8)
I think it’s fair to say most ES tutorials barely contain interesting information. They talk about Components and Systems for: position, velocity, sprite, health, and that’s about the depth of the article.
The problem is that these things are no problem whatsoever in OO, when using encapsulation/composition, where you can toggle functionality at will (using inheritance is a rookie mistake). This approach obviously doesn’t scale with the number of components, but… does it need to scale in this manner?
What would be a simple use case for a game with limited scope, where an ES clearly excels over proper OO? Given your lengthy posts, ESs are clearly saving you enough time to elaborate, yet again
lol
The “classic” use case in games is particles. There are usually a lot of them.
Cas
Indeed it’s a bummer that most ES articles are so basic. The ones that are out there for Java at least are far more speculative than data oriented in benefits and lack implementation details.
We agree that composition is the bees knees so to speak. A CA and thus ES brings implicit composition to the table and there are benefits to having this in the toolbox in addition to normal explicit composition and even a touch of inheritance, but not as a central organizational mechanism. The current Java ES implementations more or less have very limited CA features.
You get this sort of scaling for free built into a proper CA.
The chitchat monster is a demanding beast. As mentioned a limited scope game where no design changes are expected OO design is perfectly suited for the task.
The CA aspects are what really make things a pleasure during development. I’m not necessarily speaking about the currently available Java ES implementations. A lot of the killer use cases for me are modularization at the SDK / engine level which goes beyond the game focused limited CA ES implementations available.
In my efforts these are the things I like:
-
Similar to Spring and such TyphonRT features a declarative loading mechanism to load up the runtime from external configuration files. This is great because conditional dependencies are externalized and not peppered through the codebase. System components can self-wire themselves and traverse implicitly the entire runtime environment let alone ES specific use cases.
-
I use XML for the config files since it’s available everywhere, but really it can be replaced with anything including a binary format. The really cool thing especially considering Android Studio Gradle integration in regard to build flavors is that it’s super simple to provide just one XML file defining the app and completely load different functionality for product versions or any other mutation in development via providing a new flavor that simply has a different app XML config file rather than duplicating a code base. This is also very useful for multiple developers working on a shared codebase since new development can occur in parallel with less disruptions or requirements to embed factories and nasty other hacks to not disrupt developers depending on the old codebase while the new one is in progress.
-
In combination with implicit message passing it’s possible to completely separate dependencies between system components while maintaining functional connections. This isn’t new and aligns more closely with Alan Kay’s original ideas on what constitutes OO design if anything. Getting away from the listener pattern is key in conjunction with a CA / ES. This is something that bums me out when I look at Ashley since it uses the listener pattern which reduces flexibility considerably.
-
Velocity of development is much faster with a proper CA and code changes don’t require large refactorings. It’s great for rapid development where data can be implicitly passed around.
-
It makes large codebases manageable along with providing the basis for explicitly knowing dependencies of code at hand and being able to limit side effects. This valuable in testing and multi-developer environments. Get something working 100% correct and know with certainty that there are no unintended side effects with code changes not linked to the modules at hand. This includes keeping individual classes small and understandable.
-
A CA provides dynamic / functional like characteristics that run on all Java platforms maintaining type safety which is a huge boon over scripting languages in general.
-
The CA provides a standard API for accessing logic and data and there is far less method based boilerplate creation. While the concepts need to be grokked by devs it is consistent.
-
As mentioned by others data components are DAO and facilitate journalling, serialization, and concurrency concerns.
-
CA supports tooling better than rigid OOP code bases.
-
A proper CA provides the magic sauce to implement runtime modularization that goes beyond the service pattern (re OSGi). A fast / dynamic CA implementation plays nice with OSGi and other future modularization frameworks.
— I can go on, but indeed I procrastinate and have some work to get back to…
I think it’s something that once you really need it and give it a go it’s hard to return to old development practices when possible. I constantly get bummed out when I do any client work as most of it is very bottom basement regarding OO design. Re: 90’s are calling and they want their design patterns back.
Modeling particles as objects is obviously not the proper way to handle things nor is it appropriate with ES on a per particle basis.
Being able to attach to any entity a single data component with an array of particles being tracked is nice without having to add accessor methods / explicit composition.
Building a next-gen CA / ES is not easy and very time consuming… Using a well constructed one for dev for games or otherwise is liberating. A lot of this is still forthcoming for Java. I’m an early adopter so to speak. I look forward to getting my CA / ES efforts out in the wild.
The chitchat monster says I’m being baited… again… :: sigh ::
Component based and Data oriented are independent. Data oriented is pretty much required for high performance for anything over modest data sets.
The whole component based thing is really rooted in modern “best practices/design pattern-y” think…aka every thing you write is a nice reusable generic mini-library. That’s the only situation where it makes sense. You create a solution which you then apply to your problem. Flip it around into type-think. When in a specific game do you need a type which can contain an arbitrary number of arbitrary types? Answer: never. Slight change the wording of that and everything changes. For example I frequently have an entity type which can logically contain an arbitrary number of (by name) variables where are logically typeless but are concretely one of a very small number of types (like entity, int, float, string). Data segregation (for data oriented) might be handled by monomorphic access to hide implementation details at the scripting level.
This one again:
Don’t write middleware if that isn’t your product.
[quote=“Catharsis,post:69,topic:49681”]
[quote=“Catharsis,post:69,topic:49681”]
[quote=“Catharsis,post:69,topic:49681”]
[quote=“Catharsis,post:69,topic:49681”]
:emo:
Google says NO. Care to expand this acronym?
I think it means component architecture.
Cas
HA! I knew something Riven didn’t know!
Okay, I go back in my cave now.
(I really like this discussion and I really like the idea of Entity Systems, I wonder how well they work in haskell)
I really like the idea of a language which I spend zero time working around it’s deficiencies.
Actually when I mentioned “data oriented” above in regard to blog posts on ES what I meant was a metrics based approach. IE actually measuring the efficiency of various designs.
Regarding your comment above I’m sure you will not like my answer as I don’t agree with your conclusion. A component architecture (CA :persecutioncomplex:) at least in how I’ve gone about it is high level data oriented design. I’m not the only one mentioning this: http://www.dataorienteddesign.com/dodmain/node5.html
Splitting data from logic at any level of an architecture allows one to focus on how to access it and better organize it for processing. In my efforts I actually store components via meta-type and type. This allows all data components to differentiate themselves even though this is manually enforced in component design. It allows one to query a component manager (CM) / container and get back an iterator over all components stored by their meta-type (all data, all systems).
The new ES implementation in my efforts splits apart entities (which are just a CM; there is no “Entity”) and indexes all components from all entities added to the entity manager CM. This allows traversal over a particular component type for all entities directly instead of processing each fully composed entity sequentially. As previously mentioned there is also the opportunity when adding an entity to an entity manager to transform the data stored in one or more independent components and store it in a tightly packed array or perhaps byte buffer or whatever data structure that is the low level DOD form.
Basically a CA provides a high level / human accessible form to organize and manipulate data. It also can also deliver plenty of performance without transforming anything to a low level format.
I suppose that brings up an interesting distinction in classifying ES. None of this is rock solid terminology, so I’m curious if folks have any input. I’ll classify Artemis / Ashley as type I ES. Type I basically lending themselves to having distinct structure such as having specific classes like “EntityManager”, “Entity”, “EntitySystem” where processing of entities occurs sequentially in a fully composed state. A type I ES often has hard coded classification schemes baked into the API that can’t be altered. This is the aspect / family classification in Artemis / Ashley. In this case the CA elements that may be marginally present are hampered by a hard coded relationship to the classification scheme.
Type II ES are fully generic and are based on an implicit CA that has self-modifying capabilities. There is no “EntityManager” or “Entity” class that contains specialized logic or any hard coded classification scheme. The potential self-modifying behavior of the CA itself provides dynamic classification of entities along with an optional transform of high level data components to an optimized format automatically when an entity is added to the CM which is extended by its own internal system component that activates in this process. This allows for bespoke system components of the entity manager CM to work over this optimized data set of all entities at once.
In my efforts my first stab creating an ES ended in a type I ES (sequential processing of fully composed entities). Since I saw the usefulness of the approach for proper modularization I promoted the CA to be a superset of the ES implementation. I added the self-modifying aspect to the CA for other purposes not directly ES specific. In that case a way to inject a parent CM into children components without hard coding that into the CA implementation itself; IE it can be turned off by excluding the injection self modifying system from a CM. It turns out this is useful for a lot of things and definitely comes into play for a type II ES which is where I’m headed when I get back to working on the game engine aspects of my efforts.
Is that a bad thing? For JGO it seems like a poison pill for more than a few.
It’s not like we are “gleaming the scale cube” and microservicing our way to valhalla.
Technically though yeah… The implicit CA + EventBus route is similar to the microservice architecture in purpose except in process oriented. ;D
Isn’t that the promise, traditional OOP in the 90’s included, that is sold to management regarding adopting new principles and technology.
The big difference is that the traditional OOP direction didn’t deliver on this promise. Implicit CA + EventBus does.
Message passing between functional groups / modules is best followed by only passing around a generic container interface (IE in my efforts IComponentManager). This way you can pass structures implicitly between modules without leaking a ton of concrete types and poisoning the larger codebase through unchecked dependencies.
! When an artist is in control and not a coder.
-
Makes advanced tooling possible / easy
-
It’s useful when you don’t know exactly what you are building and the design may change.
-
It’s handy for event / message distribution where one message type can hold any data.
-
If your goal is to not to make a big ball of mud SDK, engine, or project / source code effort it makes a difference; supports modularity, clean code / single responsibility principle, etc.
-
It also supports high level DOD where data can be queried to be transformed into a low level representation. Hence a type II ES.
-
It also supports naturally DAO / serialization, etc. etc.
Type safety is handy for many reasons. I’m curious about the last sentence though regarding data segregation and “monomorphic access” in regard to a scripting interface because the latter is not clearly defined. “Monomorphic access” brings up connotations of immutability or marking classes or accessor methods final. Could you explain this more?
Phew, glad this is what I’m doing and is an approved machination.
It doesn’t half sound like using Java for this is like whacking a round peg into a square hole with a sledgehammer. It’s crying out for a DSL. All the complexity and cognitive blocking appears to stem from the problem that Java, the language, is totally unsuitable. Trivial example - runtime composition. There is simply no way to express this in Java at compile time let alone an easy way to compose things from separate classes. Furthermore there appears to be considerable emphasis on what amounts to manual memory layout in certain circles; again the sort of things a machine should be best placed to figure out at runtime. Java was designed to do small-scale problems in the OOP domain efficiently; you really need something completely different to express what you’re trying to do in a way that is a) efficient and b) comprehensible and c) better than the OOP paradigm
Cas
In fact the more I look at the problem the more it becomes apparent that SQL would probably have been a better place to start from.
Cas
Let me count the ways…
SQL gives us:
The ability to specify the data but hides the actual implementation of its storage and access away
The ability to specify how the data relate to each other in the form of relations
The ability to relatively quickly and easily rearrange data*
The ability to select arbitrary subsets of that data and process it in parallel
and so on. Win.
Cas
- Occurs to me that no modern SQL engine currently has any serious refactoring support. There’s an opportunity…
Except that it is and has been for almost 10 years. Generics pushed to the edges via generic methods provides the language mechanism and is supported everywhere Java is these days.
I’m actually quite excited to one day getting around to combining my Java CA w/ Scala. DSLs being driven from the Scala side of things.
In my efforts there is a well defined CA API that can be used programmatically. Annotations are used at the class meta-data level for specifying certain constraints as necessary. For runtime loading I use XML since it’s available everywhere with a basic key value organization along with conditional directives (IE Android OS version, device family, screen size, etc. etc). This allows the runtime and all conditional variations to be defined clearly externally thereby a uniform runtime. This loading mechanism could be driven by anything including a DSL via Scala or whatever is clever.
Let’s consider preparing workloads for OpenCL. One would transform the data from high level components into whatever is appropriate for the characteristics of the hardware one is running on after one queries the hardware. In the graphics case perhaps it’s slotting all relevant data from an entity into a UBO (& other buffer objects) friendly data structure spanning all entities.
Then the language evolved. It took 5 years for me to realize the possibilities and travel down this path. Generics as things go was slowly adopted in its most basic form (parameterized types) and generic method use is still not prevelent in many projects. With Java 8 we are now seeing many of the new APIs use generics in advanced ways. For 10 years though it’s been possible to do some pretty advanced stuff.
The language is evolving once again, but sadly not available on Android nor will it likely be. This is particularly even why it’s super important to pull as much from generics / annotations as possible since it runs everywhere.
It’s all there and A, B, C is well covered… Technically, I still consider what I’m doing as still OOP, but kind of more like OOPs last stand. In some ways connecting the dots from the past with an eye toward the future.
Perhaps, but the CA is a basic database. In my case a Map of Maps. One for single component storage and another that resolves to a list of components stored by type. There is a consistent query API and its fast. The CA is an in memory structure and while there may be in memory runtimes for SQL the data still needs to be transformed to the hardware one is using. It’s best to use the in memory Map of Maps in Java. The ComponentManager implementation is ~3.5k lines of code and runs everywhere Java does.
Another big win for Java and splitting out data components is that by doing this one creates schema and storage that can be versioned as necessary.
These basically ring true with a proper CA too.
And this is the big win for Java regarding the tooling available.