6 ms·
Good article. My own thinking when coding performance is also towards data oriented approaches. For example Bevy in Rust. If stuff needs to be fast, it needs to
by lordnacho 6y ago
Good article. My own thinking when coding performance is also towards data oriented approaches. For example Bevy in Rust. If stuff needs to be fast, it needs to be in cache. To do that, have everything nicely packed so you only ask for a chunk as often as you need. Then when you need it, it's already there.
Thing about old fashioned OO is it's often fine enough for your run-of-the-mill CRUD app. If you look at the latency chart he provides, there's another level to it: going to a database server. If you're gonna do that, you're not really in the speed game anyway, so why not make it simple? Just write it in the most uncomplicated way the language allows, and that's it. Waiting for the DB will take way longer than filtering the ants, forget all the optimizations.
Edit:
Forgot to mention with ECS, you have a different organizing principle. It can be quite enlightening to work with "systems that operate on data" as opposed to OO where you have the data declared next to the functions that mutate it. It can add clarity for certain problems. Somehow over the years I've found that the animals/cars analogies given in OO tutorials are one of the few places that fit well with the model.
If you imagine a game that has characters, relationships, items, spells, and so on, it's often not that easy to model as encapsulated objects. Does each character have a list of other characters that they have a relationship with? What happens when you want to change the relationship, or a character casts a spell that temporarily changes the relationship? Can easily end up a mess, because it's not obvious where such things should live in an OO model.
- rhn_mk1 6y agoTo elaborate on Bevy: the data-oriented approach used is called an Entity Component System.
- meheleventyone 6y agoAnd super importantly an ECS is not necessarily data-oriented.
- tgaj 6y ago> If you imagine a game that has characters, relationships, items, spells, and so on, it's often not that easy to model as encapsulated objects. Does each character have a list of other characters that they have a relationship with? What happens when you want to change the relationship, or a character casts a spell that temporarily changes the relationship? Can easily end up a mess, because it's not obvious where such things should live in >an OO model. It's not really that hard. You just have one RelationshipManager that every Character have access to via simple public methods. OO is not so bad, especially when you forget about inheritance and use composition.
- beagle3 6y agoAh ... here we are again. http://www.paulgraham.com/reesoo.html http://www.paulgraham.com/reesoo.html OO is not so bad because OO is not well defined.
- lordnacho 6y agoBut splitting it out into another class is halfway there already, no? The relations object doesn't correspond to a real domain object anymore. Next step is just to split out all the other components.
- dragonwriter 6y ago> The relations object doesn't correspond to a real domain object anymore. “relationship” is a noun describing a real feature of the domain, and thus is a real domain object.
- lordnacho 6y agoYou could say that about anything you construct in your model though? Stops being OO if it's not somehow similar to an admittedly vague concept of what an object is, surely? I could write the whole thing as an ECS and call all the objects systems or components, and then they are nouns in the domain?
- dragonwriter 6y ago> You could say that about anything you construct in your model though? That’s kind of the point, yes, model -> nouns -> objects. > Stops being OO if it's not somehow similar to an admittedly vague concept of what an object is, surely? There’s nothing particularly vague about “noun in the domain”; linguistic-based modelling certainly isn’t the be-all and end-all of OOP modelling, but its one of the traditional approaches dating back to the early 80s and its more sophisticated than thr kind of naive tangible-item approach that seems to be set up as a strawman here. > I could write the whole thing as an ECS and call all the objects systems or components, and then they are nouns in the domain? A relationship is a real thing in the domain being modelled, “systems” and “components” are (in the sense you are using them) not, unless the thing you are modelling is a an ECS implementation of a domain. There might be some utility for such a second-order model in OOP (if the OOP is for a code generator, for instance), but it wouldn’t be a model of the domain addressed by the ECS system, but of the ECS system itself.
- beagle3 6y ago> Somehow over the years I've found that the animals/cars analogies given in OO tutorials are one of the few places that fit well with the model. Yes, and I've never actually needed to implement a cat or a cow in any project :) The other thing for which OO works much better than plain data is GUIs - and I think it is not a coincidence that OO popularity exploded together with the the coming-of-age of GUIs. The other canonical example of OO - "Shapes" - doesn't actually work well at all; It doesn't work better with "plain data", but it exposes the fallacies of trying to use OO inheritance to model the real world. Every square is a rectangle, so square should inherit from rectangle ... but, you can't stretch width and height independently in a square, so it's not really a rectangle, etc. etc.
- deleted 6y ago[deleted]
- dragonwriter 6y ago> The other canonical example of OO - "Shapes" - doesn't actually work well at all. Shapes work quite well with OO if you design the heirarchy right using is-a relationships, the typically cited problem involves applying is-a relationships which apply to immutable geometric concept of shapes to mutable representations whose mutation contracts don’t support the is-a relationship of the immutable shapes. This would actually be fine if the contracts were written in a way which respects the it's-a relationships properly. E.g., the common Circle-Ellipse problem goes away if the contract for the mutator for, say, the major axis of an Ellipse doesn’t specify what the effect is on the minor axis. For the base class, the two can then be independent with the Circle subclass having them invariably equal under mutation. Alternatively, its not a problem with a Smalltalk like “become”, in which case an unequally stretched Circle becomes an Ellipse with appropriate attributes. Or, the mutable elements arr restricted to things thst don’t change the shape like scale and location, and shape transforms return a new Shape; if Ellipse’s StretchMajorAxis returns an Ellipse and so does Circle’s, there’s no problem, either.
- beagle3 6y agoI should have added "in C / C++ / C#". Everything OOP works well in Smalltalk (and most of it does in Python), but you pay dearly for that in efficiency - which is what this article as about.
- kstenerud 6y agoIt's mostly a question of hierarchy of data and responsibilities. Character objects that administer changing relationships are doing too much. For example, relationships involving "characters", "relationships", "items", "spells" etc can be handled by objects like "party" and "inventory" and "spell book" (glorified lists supporting a few custom named operations that basically do the same things) which contain the links to other objects. A spell that temporarily replaces your equipment simply swaps out your "character's" "inventory" object with some magical temporary one, and then swaps the real one back again when the "effect" from that "spell's" "invocation" ends. After this, adding support for something else like a spell that swaps inventory with the victim or steals their equipment would be trivial. Convert this to functional terms and you've basically got a hierarchy of data that you operate functions on and generate new data to put back into the hierarchy, matching the morphing environment. It's all about the data in the end, regardless of your source point of view.
- dragonwriter 6y ago> If you imagine a game that has characters, relationships, items, spells, and so on, it's often not that easy to model as encapsulated objects. I...disagree. ECS definitely has efficiency advantages for games in terms of support performant implementations, which for games is often overwhelmingly critical, but there’s well understand OO ways to address the modelling issue tou raise. > Does each character have a list of other characters that they have a relationship with? It probably acts like it does, but it probably doesn’t maintain it, instead deferring to a separate object that acts as a repository of relationships, which are themselves objects, which allows you to modify relationships without touching the characters at each side (which may be more than two for multilateral relationships). > What happens when you want to change the relationship, or a character casts a spell that temporarily changes the relationship? You retrieve and act on the relationship object, which knows how to handle modifications.
- megameter 6y agoThat doesn't really encapsulate anything, then. You end up with global data wrapped in classes.
- TeMPOraL 6y agoYou always have "global data" in your program - i.e. the sum of all it models. The issue is with controlling who can see and modify it. Encapsulation is a way to establish that control, and it's purpose is to minimize cognitive load on the programmer. The problem with relationship example is that modelling this is tricky, and you have to be careful - and that's regardless of the programming paradigm used. For instance, if you're talking about "a relationship between two characters" as an objective, categorical thing (e.g. "A and B are friends", "C and D are enemies"), you're thinking about a concept that's separate from the characters themselves. So you don't want your characters to have a list of other characters, because it splits a single concept into two pieces and risks creating inconsistencies (A has a "friend" pointer to B, but B doesn't have a "friend" pointer to A). On the other hand, character A may want to query the list of characters to whom they're related, so you'll want to be able to generate that list on the fly. Thus the OOP design would be: some object that manages all the relationships, that object being reachable from every character, and able to answer a query "what are all relationships for character $X?". An action modifying a relationship (say a spell temporarily making A and B enemies) would therefore act on that relationship manager too - which makes sense, because the action is modifying a relationship, a concept that's separate from characters themselves. On the other hand, if you wanted to model relationships from the point of view of your characters, i.e. what they think of another character, then such relationships stop being an independent concept. You're now modelling the subjective perspective - and thus these would belong to characters, and in OOP, you'd likely model them as a list on the character object. Whether you're doing classic OOP, plain functional programming, data-oriented programming - you still have to think about the questions described above in order to pick a correct representation for your paradigm. These questions are independent from the programming paradigm. (And to add a further example to the mix, if you're storing character data in a relational database, these questions boil down to "is 'relationship' many:many, or 1:many?", and the answer determines the kind of bridge table you'll use.)
- grayclhn 6y ago> If you look at the latency chart he provides, there's another level to it: going to a database server. If you're gonna do that, you're not really in the speed game anyway, so why not make it simple? OTOH, this is where I can see the biggest benefits from building a language/system around the article’s approach. You’d really like to execute a lot of the logic on the DB (or some other distributed system) which needs to be deeply integrated with the DB to be as convenient and expressive as pulling the data and operating locally.
- Tomte 6y ago> as opposed to OO where you have the data declared next to the functions that mutate it. In the popular OO languages. As usual, the Common Lisp Object System is always worth a look. You have classes that encapsulate data, and then you have "free" generic functions (well, methods that implement those generic functions) that operate on that data.
- kaba0 6y agoHow does that enforce class invariants?
- jayd16 6y agoI always laugh when people talk about ECS vs OO as if the entities, components and systems weren't objects. Its an OO pattern. Even in the most abstract form when entities are just ids, and you combine components and systems, you're still going to implement the component-system like an object because OO is super useful. Using arrays doesn't mean you're not using OO.
- BlueTemplar 6y agoYou're talking like ECS and OOP are separate, while ECS is actually relational OOP : https://www.gamedev.net/blogs/entry/2265481-oop-is-dead-long-live-oop/ https://www.gamedev.net/blogs/entry/2265481-oop-is-dead-long...
- smusamashah 6y agoCharacter interacts with the 'world' or another local context object which contains all characters it can interact with.