Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to properly compose functionality in systems? #1126

Open
mucaho opened this issue May 11, 2017 · 5 comments
Open

How to properly compose functionality in systems? #1126

mucaho opened this issue May 11, 2017 · 5 comments

Comments

@mucaho
Copy link
Contributor

mucaho commented May 11, 2017

Recently I've been reinventing the wheel with systems. Crafty could do that for me, if I'd use an entity composed of multiple components:

// merge inits
if (typeof layerTemplate.init === 'function') {
    var layerInit = layerTemplate.init;
    var commonInit = common.init;
    layerTemplate.init = function() {
        layerInit.call(this);
        commonInit.call(this);
    };
}
// merge removes
if (typeof layerTemplate.remove === 'function') {
    var layerRemove = layerTemplate.remove;
    var commonRemove = common.remove;
    layerTemplate.remove = function() {
        layerRemove.call(this);
        commonRemove.call(this);
    };
}

What is the preferred way of handling this? I would like to decouple code and avoid writing everything into one large system template.
Would it make sense to allow systems to be composed of components? (As they are, as far as I understand, named, lazy initialized entities. See also discussion in #878).

@starwed
Copy link
Member

starwed commented May 14, 2017

What I've done in a few places was essentially just add functionality to systems as a mixin. That's how event callback methods get added, and also how common methods for the graphics layer types get handled.

You could formalize this as some sort of "components-for-systems" concept but it's not clear to me that this would be worth the overhead. Keep in mind that component init/remove methods don't necessarily care about the order they're run in, whereas I think most of the examples for systems do care.

I do think there's another approach you could use for most of this block of code. The individual systems could just declare those methods as part of their events block -- all this common init code is doing is binding the events to specific named functions which are overridden. Those default-no-op method blocks for resize/render/setpixelart shouldn't really be necessary in an event driven system, where events can just be ignored if the system doesn't care about them.

Another technique would be to use additional events instead of the explicit merging of init/remove; that is, instead of calling the two init methods in order, you could have the main init event trigger a LayerInit method instead.

Overall:

  • You can avoid some of these problems by using event binding over explicit method calls/names.
  • I'm not opposed to adding more helper methods for system mixins, but I'm my instinct is that it doesn't require the full flexibility of the components-entity system.

@mucaho
Copy link
Contributor Author

mucaho commented May 14, 2017

What I've done in a few places was essentially just add functionality to systems as a mixin. ... I'm not opposed to adding more helper methods for system mixins

Would you kindly elaborate on that, maybe with some example code? Is that what's being done in the example I posted above?

The other suggestions make sense for this particular case, thanks. Will make a PR that:

The individual systems could just declare those methods as part of their events block ...
you could have the main init event trigger a LayerInit method instead.

@mucaho
Copy link
Contributor Author

mucaho commented May 17, 2017

instead of calling the two init methods in order, you could have the main init event trigger a LayerInit method instead.

There is a problem with this approach: event callbacks, defined in the events block, get setup after the init method is called. Is this intentional, or could this be changed to call the init method after all other special properties are setup from component / system template?

@mucaho
Copy link
Contributor Author

mucaho commented May 21, 2017

Keep in mind that component init/remove methods don't necessarily care about the order they're run in, whereas I think most of the examples for systems do care.

Btw, initialization order can be enforced with "required" components.

@michaelmelanson
Copy link

If you want to get fancy, you can use higher-order functions. As an example, say you wanted to add a more sophisticated logging that includes the entity's name in the message. You could do this.

function withLogging(c) {
    return Object.assign({
        logWithName: function(message) {
            Crafty.log(this.getName() + ": " + message);
        }
    }, c);
}

Crafty.c("MyComponent", withLogging({
    events: {
        FrameStart: function() {
            this.logWithName("Got frame start event"); // prints "EntityName: Got frame start event"
        }
    },
});

Crafty.e("MyComponent", { name: "EntityName" });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants