Git Product home page Git Product logo

rude's People

Contributors

mrrogge avatar

Stargazers

 avatar  avatar

Watchers

 avatar

rude's Issues

Finish documentation

Clearly this project is not well documented at the moment. To be honest, most of my time is being spent on developing a game built around this engine, so unfortunately I haven't been able to contribute much on this front. In the meantime, I've left several comments throughout the codebase which should hopefully offer a little help.

Handling registration collisions

Currently, when registering classes (components, systems, subcomponents, etc.) you specify a string ID. This makes it difficult to write modular code. For example, say you have one plugin that registers a component class to ID "sprite". If you want to use another plugin that registers a different "sprite" component, you can't do so without creating issues with the first plugin.

How can this be resolved? One way might be to write the plugins with config options that allow you to override the ID used for each class. This seems kinda hacky though.

Another possible solution would be to do something similar to vuex modules: https://vuex.vuejs.org/guide/modules.html#module-local-state. For example, maybe there could be rude "modules" that define a namespace for registered classes. Still, this would really just be pushing the issue up a level, because then there could be collisions between equivalent module names. This would at least make it easier though, instead of having to configure many different IDs you would only need to configure the module ID. I think for now, having a way to group registered classes into modules would be a sufficient solution.

Immutability considerations

With rude, and ECS in general, the entities/components are really one big blob of state. Every system is essentially just a function that mutates that state. That obviously comes with some pros and cons, pro being that mutations are generally quick, but con being it makes unit testing a hell of a lot harder.

I've been toying with the idea of making certain components, or parts of components, immutable. What would that look like? If I had a position component, for example, that is essentially just a vector, what would happen if I designed that vector to be immutable? Instead of mutating the component table, we would need to drop it and add a new replacement. Each position modification would then require replacing the entire component.

The biggest concern is garbage tables building up over time. My general solution to that is PoolableMixin and relying on release() calls. Maybe there could be a setCom() or replaceCom() method that automatically releases an existing component and "attaches" a new instance to the entity. This would at least be more elegant than manually calling removeCom() and addCom(). Of course, all of these replacements would add additional overhead, so I'll need to test performance and see if the benefits outweigh the performance hit or not.

Overall, I think there may be some benefits to making some components immutable, but definitely not all. I think supporting both approaches adds flexibility and allows the end user to make their own choices based on their specific needs.

Edit: one point of clarification...tables in Lua can never truly be "immutable" due to rawset(). If I'm treating something as an immutable object, I'm assuming the user only goes through the object's API and doesn't break the rules by directly changing elements.

EventEmitterMixin and determinism

The EventEmitterMixin allows a class to be extended with an implementation of the observer pattern. There are a few things about this I'd like to discuss.

First, let's talk about determinism. When I originally started building rude, I thought it would be nice to have scenes be deterministic, or at least effectively deterministic. What I mean is, for a given scene, if you load a given set of entities and components and call update() with a given dt, then the result should always be the same and not depend on any "hidden" state (e.g. globals, internal system data). This would open up a lot of possibilities for testing, both in development and maybe even in production builds.

With EventEmitterMixin, you are essentially allowing behavior to be modified at runtime. If a system registers a new handler in an update() call, future update() calls may result in different data manipulations, thus making that scene non-deterministic. I don't think this is something rude should rely on heavily. Furthermore, having a dedicated mixin for this behavior implies that a user should be applying it to their own classes, and for now there's really no reason anything but the Scene class should be using this.

It's worth noting that it's impossible for us to completely prevent a user from breaking a scene's determinism, due to Lua being such an open language. For example, one could do this:

function Scene:onUpdate(dt)
    if aBadActorAppears then
        Scene.onUpdate = function(dt) print("say goodbye to your updates!") end
    end
end

With all that said, here is what I'm proposing:

  1. EventEmitterMixin goes away. It should no longer be a dedicated mixin class.
  2. Engine should no longer have anything to do with events. For now, I don't see a benefit in having "engine events", however this may change in the future.
  3. Scene should add an emit() method. This should call an onEvent() method that is intended to be overridden by the user. This still gives full control over deciding what happens during certain scene events, and in which order things happen, but it dissuades against changing this behavior at runtime.
  4. Scene can still have the registerEventHandler() method in the class definition. However, it should be clarified in the documentation that this mechanism should be avoided unless absolutely necessary, since it makes testing the scene much harder.

rework scene events

I'm not a big fan of the current scene "lifecycle" hooks like visibility, updates, onLoad, etc. I think this could be cleaned up a lot.

One thought is to maybe embrace the event mechanism and build everything off that, including updates and drawing. But I may still want dedicated callbacks for these, not really sure at this point. I should keep in mind that the order that systems do certain actions is pretty important in practice, and I should make this easy for the user to manage. I'd like to move toward having an "onEvent" callback function that can be customized for each event ID. Just some thoughts.

git workflow?

I'm trying to decide on an overall workflow for this project. Originally it was the Git Flow model since I'm most familiar with it, but now I'm not so sure. master and dev branches seem redundant.

I also need to figure out a release schedule. How do I determine when a certain set of new features should be considered a new version? I don't want to load up the luarocks server with a bunch of versions, but I should have some sort of criteria. Maybe based on number of issues? But issues vary a lot in scope, so...I'll need to think on this.

To resolve this issue, there must be a definitive workflow plan documented in the readme.

delayed entity removal

If an enemy is removed in the middle of a frame update, sometimes this can break things. To solve this, I'm proposing a function that flags an entity to be removed at the very end of the frame.

Allow engine and scenes to be configured on initialization

It would be nice to have a built in way to set various configuration parameters on an engine or a scene. For example:

  • set the scene stack mode for engine, single or multi
  • turn a debug mode on/off, e.g. for logging and checking asserts/function contracts
  • if a scene is meant to interact with another scene, the scene ID could be a config value rather than hardcoded in the logic

My thought is to add an options param to the initialization functions of Engine and Scene. This option can be a table where keys correspond to various config parameters. Scenes could be extended by handling additional option keys depending on their needs. It might also be helpful to add a config method that allows changing these values later on.

support string entity IDs

Currently, entity IDs are only intended to be numbers. In practice however, having named entities like "player" or "camera" would be convenient. We should allow adding new entities with a string value for the ID.

Scene mode config

Originally, I intended for scenes to be completely independent and capable of running simultaneously. The reasoning was so a user could make, say, a main scene responsible for the world entities, and a separate UI scene responsible for HUD, pause menu, etc.

I still think this can be useful, but it seems like in practice, having a stack where only one scene is active at a time is a bit more common.

What I'd like to do is make each Engine instance configurable for either "single" scene mode or "multi" scene mode. this mode would affect how certain scene-related methods work. For example, newScene() may build a new scene and add it to the top of the stack of using "single" mode.

Rework alert module

I had some ideas for making the alert module more useful:

  1. The developer should be able to define custom alert messages and assign them to IDs. These alerts could then be raised by calling the module as a function and passing in the corresponding IDs.
  2. Each custom alert should have a configurable property that defines what happens when it is called. One mode could raise an error, another could print the message to the console, another could write out to a log file, and another could just do nothing.

Make return values and error handling more consistent

I haven't been very consistent with return values for functions, in particular what happens when a function "fails". I'd like to clean these up so they are more consistent across the codebase. For example:

  • some class methods return the instance, while others do not
  • some functions return a true on success, while others do not
  • when a function fails, some methods return a nil and an exception instance. Others return a nil and a string. Others simply raise an error.
  • Instead of returning exceptions, some functions just log them
  • Some functions that are loop-based may have different exception instances for several iterations of the loop. Typically these functions log the exceptions, although they could return the exceptions as a list instead. Of those two options, I'm not sure what the best approach is.

Accessing data across scenes

My original intention was to have scenes be independent from each other, however I'm starting to think it would be beneficial to allow a scene to access data from another scene. There are scenarios where this would be helpful, for example:

  • A scene that acts as a pause menu may want to access game data located in a "main" game scene
  • A scene may act as a "global repository" for state that is shared among multiple different scenes. That global scene could then be solely responsible for save file serialization, rather than managing that in all the other scenes.

It's worth noting that scenes can technically access each now by going through the engine, but it's not very elegant. I think adding a getComFromScene() method might be a cleaner way to control this access. If the scene doesn't exist we could return an exception, for example. Once a component table is returned, the system logic could then modify it however they want.

Remove scene/system plugins

I thought about it and decided having a way to add plugins to scenes and systems is not really that useful. For scenes in particular, I don't really have a good way to bring in multiple plugins that don't overlap each other, since the callbacks are designed to be overwritten in the scene definition. If I change how this works in the future, maybe it'll make sense, but now there's no reason to have a usePlugin() method for scenes or systems.

Haxe?

I'm a big fan of Lua; it's a nice, simple, powerful language. That said, there are things about it that are a bit lacking, and I've often wanted to more with my code than it can support. One example is type verification like in typescript - I've tried to do some of this my own way (see: https://github.com/mrrogge/contract) but nothing substantial. I know there are some other efforts on this front, like https://github.com/teal-language and https://github.com/TypeScriptToLua/TypeScriptToLua, but I haven't worked with these much yet and don't know if they're the right approach for me.

I've been learning about Haxe lately (https://haxe.org/) and I like what it has to offer. I think they made a lot of good language design decisions, and although I've spent a grand total of 0 hours coding with Haxe, I could see myself using it for my projects, in particular this one. One big piece that has been missing in rude is verifying that component data fits a defined schema or type, which Haxe could help a lot with.

The other nice thing is Haxe code can be compiled to Lua, so I could theoretically write rude in Haxe and still be able to use it in Lua-based projects. There would likely be caveats, and issues with 3rd-party libraries like LOVE2D, bump.lua, etc., but I think the pros may outweigh the cons.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.