What is the optional overide for Release() do in a behavior?

0 favourites
  • 13 posts
  • I can't find what Release(); actually does, or when it gets called.

    Is this when an object is fully destroyed? Is there a method to override when an object gets placed or removed from the internal construct pool? I'm pretty sure I asked this like a year ago, but I can't seem to find what I'm looking for.

  • It’s probably just a place to clean up any gpu resources that you created to use for that object. So looks like it’s mentioned if you create text to render. Probably would be a place to release textures you created too.

    construct.net/en/make-games/manuals/addon-sdk/reference/graphics-interfaces/iwebgltext

  • Normally in JavaScript unused things just get garbage collected, but this does not cover all possible kinds of resources like GPU textures, active WebSocket connections, etc. So the engine provides release methods that are called just before objects are discarded and left for GC, so you can release any other kinds of resources that do not normally GC. (It's also sometimes helpful for development/debugging purposes - clearing references, or setting a 'isReleased' flag, can help identify dangling references to released objects.)

  • Sweet thanks!

  • Ashley , Uh, just a quick clarification - was there a place to add a function to be called when an object gets added to the pool or removed?

    I don't know if it matters, but currently I stuff any variables/objects I need on a behavior instance via the constructor by means of:

    this._whateverIneed = {
    	thing : true,
    	somenum : 42,
    	etc : "..."
    } 
    

    But I wasn't sure if I needed to clean up in any specific way afterwards - or if I needed to do anything between an object being in use / out of use.

    Thanks for answering so many of my questions today!

  • I'm not sure what you mean? What pool? The code you showed will just GC normally so there's no need to specifically clean that up in a release method.

    If you want to know when another object is destroyed, you can listen for the runtime "instancedestroy" event.

  • I thought construct ran an internal pool of objects in order to recycle objects destroyed objects and improve/reduce overhead for object creation/destruction in game.

    I didn't know if we are supposed to hook into that in order to avoid re-initializing certain types of data, or how data is handled through constructs instance creation, destruction, sent to pool, returned from pool, and absolute destruction.

  • I thought construct ran an internal pool of objects in order to recycle objects destroyed objects and improve/reduce overhead for object creation/destruction in game.

    No, it doesn't.

    IIRC maybe the C2 runtime did that, but the C3 runtime doesn't, so this hasn't been the case for years. JavaScript and modern GCs are so good that kind of pooling approach doesn't matter any more.

  • IIRC maybe the C2 runtime did that, but the C3 runtime doesn't, so this hasn't been the case for years. JavaScript and modern GCs are so good that kind of pooling approach doesn't matter any more.

    Respectfully and in humor, lol.... What?!

    No way! For games that it matters for, it does matter alot. For the rest, it doesn't hurt any. A generic pool usually always helps performance for any objects being created/destroyed at runtime. Tweaking, or supplying specifics can improve a pool even further.

    Allocating and releasing memory takes cpu work, no question. Better gc doesn't really change that, just lessens the impact... but it doesn't eliminate the impact. Call me a skeptic - but industry standard is still to pool - and that is regardless of whether you are in c++, c#, js, or heaven forbid gsl. If you pool in unity or unreal, I can't see a reason why you wouldn't in js in construct, given c#/c++ has better performance at runtime.

    You can easily test this with an objectpool made out of construct events using inefficient arrays and pick by uid, and it will perform better than simply creating and destroying objects, despite the pool having a large overhead for looping objects. I still get 20% better performance in this case. If you perform the same equivalent loops to the non-pooling method, the performance increases. Move the pool to scripts and you will see bigger benefits. I didn't do it in script form because I still have to lumber through js and the api, but I'm sure you could whip something out to test in minutes.

    For a bullet hell game or any other where objects are frequently being destroyed and created, object pools are a absolute must for smooth performance.

    If I run equivalent loops for pooled/vs non pooled objects to eliminate event overhead for loops, I can get 5x the fps for pooled objects in perfect contexts. Engine side, you should be able to do even better by properly moving those objects to a space where nothing updates for them and they aren't "technically" on the layout anymore.

  • Well, C3 doesn't pool objects, and it still significantly outperforms C2 on many benchmarks, including intensive destroy/create benchmarks, so it's not like it's made C3 slow.

    In Construct, there are two different things that happen when creating an object: firstly allocating the necessary memory, and secondly running a bunch of engine code to initialize the object. It's important not to conflate these things. Object pooling only helps with allocating the necessary memory. Even if you get an object from a pool instead of allocating a new one, the second task of running a bunch of engine code to initialize the object still has to be run. And that is much more work than allocating a bit of memory, and so object pooling has little benefit to improving performance there. If you make a Construct project that recycles objects, note that is not running the engine initialization code as it is keeping the object alive, so that will measure much faster, but mainly because it's not running the engine initialization code when creating objects. So if you want maximum create/destroy performance, you'd still be best off doing it in the project, as you can design the project to work with objects that continue to exist, whereas the engine cannot do that. Pooling also has downsides: it adds complexity right the way through the engine, as it means class constructors run at allocation rather than initialization, so all the initialization stuff needs to be moved to a separate method; it opens up a whole class of bugs where a created thing has the wrong state because it was recycled and something wasn't reset correctly, plus memory leaks from pooled objects holding on to things that weren't reset; it opens up memory management questions about just how many objects can be held in the pool and for how long; probably some other stuff I've not thought of.

    So C3 doesn't do any in-engine pooling for entire objects, and I think time has shown that to be the correct decision - it just doesn't matter, and even if it does, doing it within the project is the best approach anyway. I think the engine does pool things in a couple of specific cases like for individual particles. I still doubt it makes much of a difference. The main problem with JavaScript performance these days is that everyone continues to underestimate it, when you can usually write basic obvious code and it'll be competitive with C++/C#.

  • Thanks for the details. I suppose that all makes sense,

    As I understand, you are essentially saying the cost/benefit ratio also doesn't exist engine side, due to increased bugs/complexity without a "real" benefit to most games. I agree given your explanation.

    2 things I disagree with though:

    1. Making a pool via events isn't worth it. The cost of the event editor overhead eats a major portion of the performance gained, and its nigh impossible to create a generic one that doesn't spend more energy than it saves. This activity is much better to be done in script - but the cost of tying that into the editor can still outweigh the performance gained IF you use js blocks. If everything is in script it and you don't need to make recycle calls from events, perfect.

    For that reason, I would argue it isn't worth the time, because the cost/benefit ratio just isn't there.

    2. GC is most performant when collecting newly created items. I think the vast improvement in JS gc is in this realm, yes? But recycling is good because it helps major collection (though total allocations has an ongoing cost). If the bulk of gc allocations are items created every tick, the collection of those items is much, much faster. These items are part of a minor garbage collection and typically get collected in the idle time each frame, so have no impact on frame or stutter whatsoever. However, if performance is being impacted by minor gc, reducing it is as easy as not discarding obj refs every tick and not creating objs just to discard them every tick.

    Items that remain in memory for longer get collected by the major collector and those can indeed cause frame drops when it runs. Object pooling helps most in this arena.

  • I didn't mean to suggest object pooling via events isn't worth it - if you do want object recycling then that is the most efficient way to do it. However you'd probably be surprised how rarely you need to do such things. As Performance Tips says, the best thing to do is the most easy and obvious approach, and only optimize if necessary.

    Modern JavaScript GCs are very sophisticated. Generational collectors do make it especially cheap to handle short-lived objects (minor GC). However modern JS engine's major GCs are still parallel (using multiple CPU cores), incremental (running small jobs intermittently instead of one big stop-the-world pause to avoid jank), concurrent (running simultaneously with JS execution, preventing risk of jank), and also where possible schedule work in idle time to avoid interrupting smooth framerates. The V8 blog has some good entries talking about this, such as this 2019 blog post, and bear in mind there's been ~5 years more improvements since then. Overall the state of modern JS GCs is so good that I don't think I've seen any meaningful performance impact from GC in Construct games for a few years now. I just write modern engine code ignoring GC, and everyone's games are running perfectly smoothly, and where any problems come up, they are not because of GC. So, it feels pretty close to a solved problem really. I don't know if C#'s GC is as good - I would not be surprised if it was not as sophisticated, and so some people do have to code around it to some degree, but in my opinion that is not necessary with JS.

  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • Okay! Sounds like this case is nicely wrapped up, and has been for a while. I will do as you suggest.

    You may well be right about gc in c# not being as well optimized - and that sounds like an excellent rabbit hole research project for next time I am distracted :D

    Thanks!

Jump to:
Active Users
There are 1 visitors browsing this topic (0 users and 1 guests)