Ruskul's Forum Posts

  • Not at all! A memory leak in a managed language occurs when you have objects that can't be garbage collected.

    I said I wanted to avoid excessive garbage collection. "Accumulation", is a bit misleading, because you have memory that is either garbage, or it isn't, and it is basically defined as garbage by the collector. If the garbage collector marks something as garbage, it will be collected and so can't accumulate. You can create garbage, which will be collected, but a memory leak happens when the programmer has somehow mismanaged memory so that it can't be collected.

    Garbage collection (major/minor/whatever) takes time to operate, which is why I like to avoid it if I can. The more garbage you create, the more time it will take to collect. Long lived objects typically have a larger impact when removed, and there is an overhead per object that the garbage collector has to scan.

    A memory leak is a logical problem, in which the programmer has created references to objects that are no longer needed, but the references still exist on an object that is needed. A simple example would be if a character stored a reference to every bullet they shot in a list... Perhaps they have an ability that when triggered causes every bullet in the air to explode... Then when the bullet hits something, and is "destroyed" the programmer forgot to delete the reference in the list. Basically, you now have objects no longer in use, but they can't be garbage collected because the program still has a reference.

    In something like c++, a memory leak is way easier to accidentally create, because there is no garbage collector. C# is like js in that is has was to create leaks using action/delegates/lists/etc, where any function pointer can keep an object alive to which the function points, which in turn is keeping other objects alive, and so on.

    Memory leaks is simply memory that can't be released, despite no longer being in use. In managed languages, it just means that memory can't be garbage collected, though the collector will still scan it for opportunities to collect it.

  • Wait, now I'm super confused because this may indeed apply to me. This would be in instance.js

    I add properties like so:

    constructor(behInst, properties)
    {
    	super(behInst);
    			
    	//This is what the example on github shows:
    	//this._testProperty = 0;
    			
    	this._vx = 0;
    	this._vy = 0;
    	//etc...
    }
    

    I also do this as well, outside of the constructor obviously, but still inside the main instance class :

    get _someproperty() {
    	return this._somecoolmath(42) + this.somethingelse + this.etc;
    }
    _somecoolmath(n) {
    	return (n === 42? n : 0);
    }
    

    Later, I us a weakmap in the scripting interface to provide a way to hook in editor side like so:

    set vx(n) { map.get(this)._SetVx(n); }
    get vx() { return map.get(this)._GetVx(); }
    set vy(n) { map.get(this)._SetVy(n); }
    get vy() { return map.get(this)._GetVy(); }
    setVelocity(x,y) { map.get(this)._SetVelocity(x,y); }
    

    Is this not right? The example behavior on github doesn't really show anything else. I'm not sure what all constitutes a runtime class, but... ?

  • Try Construct 3

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

    Try Now Construct 3 users don't see these ads
  • I was asking about the lifespan of a construct object and how it is recycled behind scenes, if at all. Not so much worried about a leak, rather excessive garbage collection.

    Ashley addressed this in another forum recently, and as it turns out c3 does not use object pooling, Construct 2 used object pooling (to reduce the amount that the garbage collector would have to sort), but that is not the case with construct 3.

    As I understand it, Ashley chose not to bother with it because the advantages of pooling only really apply when 100s of objects are being created/destroyed every second; For those specific cases, its best to create low overhead objects and create a custom pooling solution yourself, as a generic one may not be as useful anyway.

    The advances in garbage collection in JS is vastly superior compared to 10 years ago when c2 used pooling.

    Garbage created on a frame to frame basis is handled with ease, typically in downtime between frames, and as such, it isn't critical to worry about creating JS objects to discard them each frame like it once was Major garbage collection is another beast and can cause some issues if a programmer is absolutely careless, but outside of creating large arrays and tossing them every few frames, there isn't too much to worry about. Even major GC is still effeciently handled these days in JS... My knowledge at the time of asking the question was about 10 years behind the times

    All that said, while object pooling isn't 100% necessary anymore, as it once was, I have found that creating and destroying a large amount of objects every tick still has a large enough impact to recommend recycling/pooling.

    Trying to manage object recycling via eventsheets, however, usually doesn't provide a large benefit as a consequence of increased event overhead. Therefore, I recommend either creating a behavior/plugin to handle it, or js module if you already have a lot of JS going on.

  • Ashley, I think you have made your position much more clear in the last few responses, so thank you for taking the time to do that.

    Out of respect for your position, I will do my best to shift over to only documented API.

    I don't think anyone here is arguing that encapsulation is bad.

    I think as is being pointed out, hacking always, eventually, inevitably, ends in disaster. I don't think anyone disagrees with that, and I don't think anyone is making tools thinking they will never have to update them at some point, maybe entirely!

    I think the point is this; and it isn't incompatible with your position:

    The current sdk api is too limited to realize certain types of projects. I would say it affects most projects, if you discount simple arcade games and prototypes, or learning projects.

    So long as the encapsulation occurs alongside (and preferably after the efforts to expand the api), that would be great! It would be welcome!

    But so long as you have to resort to internal engine hacks to achieve what official behaviors achieve, then the sdk is objectively too limited. The official behaviors, simple as some are, are demonstrative of the official sdk limitations.

    I understand there are some api that need to stay internal for flexibility reasons. But I also see many that just look like oversights - missing for no reason other than lack of priority.

    As you say, industry standard is too use encapsulation, but every other tool has a larger more robust api.

    Just! Please don't obfusicate the internals, just to enforce "good practices"! As I see it, at best that is like the fire authority deciding to torch every building not up to code, in order to prevent the eventual fires that could happen.

    The internals provide a very useful learning tool, even if not abused, I would still like that access. If I couldn't see the internals in c2, I never would have been able to make any behaviors. The same thing goes for c3. The current documentation and example list is just far too limited and the internals become a very useful learning ground. Being able to pop the lid and have a peak really helps to improve understanding and inform practices.

  • Doh! I totally missed that! Thank you kindly!

  • Thanks Ashley ! I think you mentioned using mixins in the past and I forgot about that feature.

    Incidentally I was looking at the latter approach you mentioned. Thanks for the insight.

  • I wrote a new timer behavior that uses real triggers https://www.construct.net/en/make-games/addons/1236/trigger-timer it outperforms the build in timer behavior in all cases I have tested and also only has one instance picked in the on timer trigger. (I also added some additional features)

    Sweet, I'll have to check it out. I'll probably stick with what I have at this point, but I may use yours in the future.

  • It's kinda funny that on almost every topic I read Ashley say "dont worry about performance. difference is not really noticable. 99.9% of projects doesnt need this" and so on. And then guess what? Peformance was literally the main problem i had with C3 through all those years. And i make pretty simple games! Some things in C3 cause absurdly bad performance and you will never read about that in docs, and the only way to figure out is to extensive testing and comparing different approaches. Without WebGL debugger and literal months of trial and error i would've never made my games playable on mobile or low end devices.

    In most cases i agree with Ashley, but...

    Ashley "dont worry about performance" Gullen

    Yeah, I struggle with his responses: "Usually this won't matter" -links article about preoptimization. The only reason I noticed it was a problem was because it was a problem, whether that's usual or not. I too moved many a project prototype from contruct to unity because of performance.

    In unity, I would just make the game and it would run well enough. Construct I always felt like a nes developer in the 90s squeezing everything I could out of the system.

  • I can't find in the manual if it tells you how to add an interface for a behavior.

    I did the following in the behavior instance.js:

    const map = new WeakMap;
    self.IToggleBehaviorInstance = class IToggleBehaviorInstance extends IBehaviorInstance {
     constructor() {
     super();
     map.set(this, IBehaviorInstance._GetInitInst().GetSdkInstance())
     }
     
    		getState(i) {
    			return map.get(this)._GetState(i);
    		}
    		setState(i, v) {
    			C3X.RequireFiniteNumber(i);
    			C3X.RequireFiniteNumber(v);
    			return map.get(this)._SetState(i,v);
    		}
    		
     };
    

    Later in an event sheet, I have the following:

    const inst = runtime.objects.Sprite.getFirstInstance();
    const t = runtime.objects.Text.getFirstInstance();
    
    const toggle = inst.behavior.Toggle;
    
    toggle.setState(0,3);
    t.text = toggle.getState(0);
    

    But I get an error saying: TypeError: Cannot read properties of undefined (reading 'Toggle')

    I've looked at the official behaviors, and I don't see what I am doing wrong (unless you just aren't allowed to define interfaces?)

  • Hey all,

    I have no issue sub classing an object type, but it seems the same thing doesn't work for families, unless I am doing something wrong.

    I want to be able to define a common behavior (in this case, some custom physics stuff), and apply it to many objects.

    Currently, as I understand it, I can only subclass a particular object:

    //In familyExtend.js
    export default class TestInstance extends globalThis.InstanceType.Tester
    {
    	constructor()
    	{
    		super();
    		this._balls = 2;
    	}
    }
    
    //In familyExtend2.js
    export default class TestInstance2 extends globalThis.InstanceType.qCollider
    {
    	constructor()
    	{
    		super();
    		
    		//add properties
    		this._balls = 2;
    	}
    }
    
    //In main.js
    import TestInstance from "./familyExtend.js";
    import TestInstance2 from "./familyExtend2.js";
    
    runOnStartup(async runtime =>
    {
    	//extend classes like so
    	runtime.objects.Tester.setInstanceClass(TestInstance);
    	runtime.objects.qCollider.setInstanceClass(TestInstance2);
    	//...
    	runtime.addEventListener("beforeprojectstart", () => OnBeforeProjectStart(runtime));
    });
    

    In the above case, Tester is a family, and qCollider is a object. I can subclass the object, but not the family. I imagine this is expected -...

    But what I don't understand then, is how do I add this functionality to every object in my game, short of redefining a subclass for every object type?

    I understand I am essentially wanting functionality that multiple object types share, but adding it on as a subclass is backwards. Like defining rats and bats and then creating subclasses for mammals.

  • Thanks, much appreciated!

  • Uh, I'm showing my js noobery here, but for the life of me, I don't understand this entry in the manual:

    *pickedInstances()

    Iterates over the instances that have been picked by the event's conditions. This is only useful with scripts in event sheets.

    How do I use it?

    For general instances I would do something like this:

    const enemies = runtime.objects.enemies.getAllInstances()
    
    enemies.foreach((i) => {
    
    	Utils.DoSomething(i, 60*runtime.dt);
    
    });
    
    //or
    
    for(let i = enemies.length(); i>= 0; --i) {
    	Utils.DoSomething("42");
    }
    

    I tried to look up iterators for js but didn't really get what construct is doing here. Any help is appreciated!

  • You can also add in predicting aim for more accuracy and handle things like predicting acceleration.

    So, for example, since the turret behavior doesn't track or care about the difference over time in the delta between target positions, a constantly accelerating target will never be hit.

    Currently the behavior tracks changes in position from one frame to the last, and uses that to extrapolate where to shoot. It also accounts for its own speed I believe.

    But if you add a variable that stores one more previous position, you can find the difference in deltas between the 2 steps between the 3 positions and roughly account for any continuous acceleration.

    You can get this working with events by using a "ghost" target. Instead of tracking a target itself, you track a sprite that you then set to the position of the intended target. you then monitor the intended targets acceleration from frame to frame, and the distance to the turret. Using the same sort of math, you can place the ghostTarget ahead of the actual target and have the turret shoot at that. The turret will practically never miss except when an object is changing directions... and you can add another layer for dealing with delta differences of the differences, and so on recursively as many frames as you like. If the target takes time to accelerate and decelerate, it won't stand a chance.

  • This is the code from the behavior, if you don't know anything about code, I can try to step you through what is happening:

    let targetWi = this._currentTarget.GetWorldInfo();
     let targetAngle = C3.angleTo(wi.GetX(), wi.GetY(), targetWi.GetX(), targetWi.GetY());
     if (this._predictiveAim) {
     	const Gx = wi.GetX();
     	const Gy = wi.GetY();
     	const Px = targetWi.GetX();
     	const Py = targetWi.GetY();
     	const h = C3.angleTo(Px, Py, this._oldTargetX, this._oldTargetY);
     	if (!this._firstTickWithTarget)
     		this.AddSpeed(C3.distanceTo(Px, Py, this._oldTargetX, this._oldTargetY) / dt);
     	const s = this.GetSpeed();
     	const q = Py - Gy;
     	const r = Px - Gx;
     	const w = (s * Math.sin(h) * (Gx - Px) - s * Math.cos(h) * (Gy - Py)) / this._projectileSpeed;
     	const a = Math.asin(w / Math.hypot(q, r)) - Math.atan2(q, -r) + Math.PI;
     	if (!isNaN(a))
     		targetAngle = a
     }

    worldinfo has all the important bits about an objects size, location, etc... and you can look it up in the addon sdk manual. The important math parts here are at the bottom with const s, q, r, and w. (const just means a variable declaration is happening)

    If you ever want to take a deep dive into what is happening behind the scenes, you can launch a game and press f12 to bring up the devmode for the browser. You can browse all the runtime code to your hearts content, and it is indeed a really valuable tool to begin understanding precisely what is happening, and how things work under the hood. You can learn alot, though, I would recommend a code base with comments if you are just learning coding.

  • So, I launched the editor and selected my cloudfile. I hit load. I actually got the file loading before the editor even realized I didn't have a behavior loaded. The load was interrupted by the editor halting with the red screen to warn me a plugin was not installed (I had shutoff my addon server).

    So I closed the editor, launched my server, and attempted to open the file, but it "Failed to open project. Check it is a valid Construct 3 single-file (.c3p) project."

    The file has just been open earlier in the day no problems. Is there anything I should check for in the capx?

    I don't have a backup, this was just a little template project I was making, so no big loss, but I would hate to have this happen to a main project.