Not logged inOpenClonk Forum
Up Topic Development / Developer's Corner / How are you using the RemoveObject-zeroes-variables feature?
- - By Günther [de] Date 2010-10-29 20:26
With references gone, objects are the only user of C4Value::NextRef. And the only purpose is to keep a list of all variables that contain a given object so that they can all be set to nil when the object is removed. I suspect that removing that list could speed up C4Script a bit. The engine already has machinery in place to deal with references to removed objects: They are kept until the end of the frame and then removed. We could easily extend that to keep objects that have references from script. That would have observable implications for scripts, though, so I'd want to study scripts that rely on variables containing an object changing to nil on RemoveObject/Explode/Sale/etc. of an object. Does anyone know examples? Do you routinely check a variable for nil to find out whether an object still exists, or do you use the Destruction callback?
Reply
Parent - - By Zapper [de] Date 2010-10-29 21:06

>Do you routinely check a variable for nil to find out whether an object still exists, or do you use the Destruction callback?


You would usually check it for nil.
Some examples; Hitchecks:
pEnemy->DoEnergy(-1000000);
if(pEnemy) // not disintegrated by BFG
   pEnemy->PlayOuchSound();

pEnemy->Deflect(pProjectile);
if(pProjectile) // not removed by a shield
    pProjectile->Hit();

Stuff like that where you just cannot overload the object for some Destruction callback.
Are the references not using some kind of magic number? So that you can quickly see whether a reference is valid?
Parent - - By Günther [de] Date 2010-10-29 22:09

> pEnemy->DoEnergy(-1000000);
> if(pEnemy) // not disintegrated by BFG
>   pEnemy->PlayOuchSound();


That wouldn't work because of corpses.
Reply
Parent - - By Zapper [de] Date 2010-10-30 08:43
I didn't say pEnemy was a Clonk who had a corpse..
Parent - - By Günther [de] Date 2010-10-30 17:25
No excuse for assuming that the enemy will never be changed to have one.
Reply
Parent - By Zapper [de] Date 2010-10-30 22:44
The Metal&Magic Swamp Fly explodes into green slime rather than having a corpse.
Would be annoying if you could not use the Clonk Rage arrows against every animal, no?
Parent - - By Günther [de] Date 2010-10-30 20:57

> pEnemy->DoEnergy(-1000000);
> if(pEnemy) // not disintegrated by BFG
>    pEnemy->PlayOuchSound();


Also, calling PlayOuchSound on dead objects is harmless, because those don't play sounds. So you could simplify that to

pEnemy->DoEnergy(-1000000);
pEnemy->PlayOuchSound();
Reply
Parent - - By Zapper [de] Date 2010-10-30 22:52

>Also, calling PlayOuchSound on dead objects is harmless, because those don't play sounds


Sorry, I thought it was clear that my "PlayOuchSound" was standing for any other arbitrary method. You could for example poison the enemy with an extremely loud and annoying "OGM YOU ARE POSION" sound. Or you could want to set a building on fire when you damaged it and it did not explode into smithereens.
Parent - - By Günther [de] Date 2010-10-31 01:27
Unconditionally setting the building on fire works fine, even if it was removed from the landscape. "Works fine" meaning "does nothing", of course. And if the ouch-sound would play on the enemies Clonk, again the sound would be silently ignored. Checking for object removal before playing a global sound would be required, yes, but again a simple "Is the Clonk Alive()" is universal for both dead and removed Clonks.
Reply
Parent - - By Zapper [de] Date 2010-10-31 09:29

>"Is the Clonk Alive()" is universal for both dead and removed Clonks.


Sure, but not for buildings or vehicles

>Unconditionally setting the building on fire works fine, even if it was removed from the landscape


Then you should probably explain to me what would actually happen if I use pObj->DoSth() and pObj is a dead reference, because I seem to have misunderstood it.
Parent - - By Günther [de] Date 2010-10-31 12:41
It works like obj->DoSth2 with a not-removed object and func DoSth2() { RemoveObject(); DoSth(); } works now.
Reply
Parent - - By Ringwaul [ca] Date 2010-10-31 18:34

>and func DoSth2() { RemoveObject(); DoSth(); } works now.


Cool stuff. :D
Reply
Parent - - By Newton [de] Date 2010-10-31 18:57
Whats cool about that?
Parent - By Ringwaul [ca] Date 2010-10-31 19:12
If I am reading this right, this allows actions to be before or after the RemoveObject() call?
Reply
Parent - By Caesar [de] Date 2010-10-31 19:13
You could examine stuff that happens in the removal routines. Also, certain dangers like accessing functions or locals after the removal (in most cases from the objects context) would be gone.

I though think it's weird.
Parent - By Günther [de] Date 2010-10-31 22:53
Selective quoting doesn't make the engine work better than it does, though. We fixed most of the obvious crashbugs regarding removed objects, but there are likely more lurking. Better to expose and fix them than continue to let them remain obscure edgecases.
Reply
Parent - - By Sven2 [de] Date 2010-10-29 22:01
How do you zero objects when they are actually deleted then? Go through all possible places where there can be pointers and delete them? That sounds like a lot of work (and I was glad it was taken from us with C4Value). It's not just the performance consideration (having to maintain the linked list vs having to search through all C4Values), but also the maintainability. Whenever a new C4Value field is added, it has to be regarded in ClearPointers because it may hold an object?

Secondly, from my (CR-)scripting experience, if you are writing scripts that need to interact a lot with unknown objects - i.e., the original pack - you have to deal with the case of objects being deleted by a callback a lot. I find myself doing plenty of those if (!obj) return;- sanity checks. I do not like the idea of keeping pointers to dead objects, because that smells like trouble.

Just imagine stuff like an object creating a helper object in construction and deleting it in destruction. If a pointer to the dead version of the object is kept, the object has to assume that functions in it are executed past the Destruction callback, so it has to assure the existence of the helper in every callback.
Parent - - By Günther [de] Date 2010-10-29 22:30

> Go through all possible places where there can be pointers and delete them?


That would be another option. It's not as bad as it sounds - we need to denumerate all those places anyway, so the code to loop through every C4Value is already written.

But I actually want to try simply keeping those objects around and refcounting them, like arrays or strings.

> I find myself doing plenty of those if (!obj) return;- sanity checks. I do not like the idea of keeping pointers to dead objects, because that smells like trouble.


Well, my proposal would allow you to get rid of a lot of those sanity checks, because the dead object would still be around and you could do whatever you want with it. The engine has to support that today anyway, because the this pointer isn't cleared, so all engine functions already have to work on dead objects. The only sanity check needed is to ensure that the object hasn't teleported itself to 0/0 if that would affect stuff.

> If a pointer to the dead version of the object is kept, the object has to assume that functions in it are executed past the Destruction callback, so it has to assure the existence of the helper in every callback.


The helper object would stay around as long as the main object was kept around, so the main object would not need to check for it's existence. That's an improvement on the current situation, where the helper object could suddenly vanish completely without any warning.

So your stories seem to suggest that not zeroing objects would simply move the failure scenarios around. Interacting with dead objects certainly isn't the most tested corner case of the script interface, so the engine bugs would surface sooner. But as for script, whether one is surprised by a variable containing a dead object or a variable containing nil doesn't make a huge difference.

It might be too late to change this because a lot of script already relies on the current behaviour, but perhaps we can still make the change internally and hide it by making dead objects evaluate to false. Probably an experimental implementation to see what breaks is called for.
Reply
Parent - - By Zapper [de] Date 2010-10-30 08:46

>That's an improvement on the current situation, where the helper object could suddenly vanish completely without any warning.


Uh, no. The helper could still suddenly vanish without any warning but you would notice it at a different place. Or did I get you wrong?
Parent - - By Günther [de] Date 2010-10-30 17:28
If somebody removes your helper object, things will go wrong anyway, and the correct response depends on the kind of helper. Stuff like windwings can just continue to be manipulated - it doesn't do anything if it's dead, but doing nothing was probably the purpose of removing the object from the landscape.
Reply
Parent - By Zapper [de] Date 2010-10-30 22:56
So then your change would actually not change anything about that helper because if it gets removed everything breaks anyway. But I don't get how it is an improvement.

Do you mean that it is an improvement because you would "have" to implement an destruction callback (maybe even with an appendto to the script) because otherwise nothing would work?
Then your improvement would be as much of an improvement as an entry to the docs saying "Please use a callback based architecture between your objects", imo
Parent - - By Sven2 [de] Date 2010-10-30 09:32
The sanity checks are obviously not superfluous, because you do not want objects to interact with dead objects. Projectiles and weapons shouldn't hit dead objects. Floor switches shouldn't be affected by dead objects. Animals shouldn't follow dead objects. Fire should stop burning when its object is dead and dead objects shouldn't be able to catch fire again.

Just about any object interaction would need to check for dead objects (just like the engine does all those status-checks) AND still check for zero pointers in most cases.
Parent - - By Günther [de] Date 2010-10-30 20:54

> Projectiles and weapons shouldn't hit dead objects. Floor switches shouldn't be affected by dead objects.


Those would use FindObject and not be affected either way.

> Animals shouldn't follow dead objects.


They shouldn't follow objects that are simply out of reach in other ways either. Checking for Teleportation or for the objects not being Alive() automatically covers dead objects.

> Fire should stop burning when its object is dead


Effects rely on callbacks for that, or for the effect to simply be removed with the object.

> and dead objects shouldn't be able to catch fire again.


They can't get effects, so that's already covered.

> Just about any object interaction would need to check for dead objects (just like the engine does all those status-checks) AND still check for zero pointers in most cases.


Well, not in the examples you listed. Also, checking for nullpointers would only be required if you want to guard against some other script tampering with your local variables. In my opinion, it's the responsibility of the script setting a variable to ensure that nothing bad happens. So if your interaction consists of obj->SetRDir(), you now need to check for nullpointers, but you don't need to do that anymore with the proposed change, because SetRDir() is harmless on dead objects.
Reply
Parent - - By Zapper [de] Date 2010-10-30 23:01

>because SetRDir() is harmless on dead objects.


What would those "dead objects" be? I think I could have misunderstood you.
If an object is removed and the locals holding a reference to said object are not automatically set to zero, the object is considered "dead", right? And when SetRDir internally tries to deference the handler it would notice that some magic number (?) does not fit anymore and the object does not exist anymore and therefore do nothing?
That's how I understood that so far.

(Because using "dead" as "not alive" would not make much sense since objects or vehicles are never alive but can still break after damage)
Parent - - By Günther [de] Date 2010-10-31 01:49 Edited 2010-10-31 01:53
Yeah, using "dead" for an object which has had RemoveObject() called on it is an unfortunate choice given Kill(). Sorry for that. I meant SetRDir is harmless on removed objects. It would probably still set the rdir - I didn't check -  but the object is not in the list of objects in the landscape, so nothing interprets that rdir anymore. Various other engine functions silently do nothing on removed objects, do guard against objects that do
this->Explode(); this->MakeCrewMember();
or similar things.

There's a field named Status in every object that tracks whether the object has been removed. It's also used for ScenarioSections for some reason, so you can call GetObjectStatus to verify that this->Explode() does indead remove this.
Reply
Parent - - By Zapper [de] Date 2010-10-31 09:30

>There's a field named Status in every object that tracks whether the object has been removed. It's also used for ScenarioSections for some reason, so you can call GetObjectStatus to verify that this->Explode() does indead remove this.


Wouldn't it then be possible to let the reference evaluate to false when cast to bool?
Parent - - By Günther [de] Date 2010-10-31 12:49
Perhaps, but covering all the cornercases like obj > 0 would be tricky.
Reply
Parent - - By Zapper [de] Date 2010-10-31 15:24
True, but that should be forbidden anyway! I mean, you would need something like IsValid(pObj) (because IsAlive does not work - see vehicles/objects) and you could as well do that with a comparison with false. If you compare an object to a number you can not expect meaningful results (can you throw an error at parse time there?)
Parent - - By Günther [de] Date 2010-10-31 16:31
At parse time, the engine mostly doesn't know the types of expressions. I implemented some warnings for a few easy cases like Message(42), but for func foo(bar){Message(bar);} the engine cannot determine what bar is. (I suspect totally typechecking C4Script might be equivalent to the halting problem, which has been proven to be unsolvable.)

Okay, I think obj>0 actually already throws at runtime, but there are more complex examples. It's not impossible, I'm just not sure whether it's worth the effort.

And the function to check Status is already called GetObjectStatus. We might want to introduce a shorter alias, though.
Reply
Parent - By Zapper [de] Date 2010-10-31 16:44

>And the function to check Status is already called GetObjectStatus. We might want to introduce a shorter alias, though.


"!"? ;)
I mean, a big "DON'T USE ANYTHING ELSE ON OBJECT REFERENCES" in the documentation should suffice(?)
Up Topic Development / Developer's Corner / How are you using the RemoveObject-zeroes-variables feature?

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill