Game crashes when my custom tower tries to destroy its partner

Started by LordBiscuit, January 24, 2014, 06:56:41 AM

Previous topic - Next topic

LordBiscuit

Hello there! First time programming crpl
I have created a custom tower that transfers creeper from itself to its slave (or vice versa) to work as a portal.
I have two scripts: portalMaster and portalSlave, both of which have a once that is supposed to find their partner and save the partner's UID as a variable
I want destroying one to also destroy the other, so I wrote this little :destroyed function:
Quote
:destroyed
   <-partner 2 Destroy
It seems too simple to get wrong, but somehow I did. UID is correct (had them both print self and partner to trace).
The complete scripts:
Master:
Spoiler

# portalMaster.crpl
# Created on: 1/24/2014 10:33:22 AM
# Author: LordBiscuit
# ------------------------------------------

$portalID:0
$multiplier:0.5
$interval:0

once
   ClearTraceLog
   HideTraceLog
   
   "portalID" Dup <-! GetCoresWithVar
   ->numOfCoresWithSamePortalID
   <-numOfCoresWithSamePortalID 2 neq if
      ShowTraceLog
      "ERROR: number of cores with same portalID != 2"
      Trace
      <-numOfCoresWithSamePortalID Trace
   endif
   <-numOfCoresWithSamePortalID 0 do
      ->unitUID
      <-unitUID self neq if
         <-unitUID ->partner
      endif
   loop
   <-partner CONST_COORDX GetUnitAttribute ->partnerX
   <-partner CONST_COORDY GetUnitAttribute ->partnerY
endonce

CurrentCoords GetCreeper
<-partnerX <-partnerY GetCreeper
Sub # C(self) - C(partner)
<-multiplier Mul
->amount # amount to transfer from p1 to p2
CurrentCoords 0 <-amount Sub AddCreeper
<-partnerX <-partnerY <-amount AddCreeper
<-interval Delay

:destroyed
   <-partner 2 Destroy
[close]
Slave:
Spoiler

# portalSlave.crpl
# Created on: 1/24/2014 10:33:26 AM
# Author: LordBiscuit
# ------------------------------------------

$portalID:0

once
   ClearTraceLog
   HideTraceLog
   
   "portalID" Dup <-! GetCoresWithVar
   ->numOfCoresWithSamePortalID
   <-numOfCoresWithSamePortalID 2 neq if
      ShowTraceLog
      "ERROR: number of cores with same portalID != 2"
      Trace
      <-numOfCoresWithSamePortalID Trace
   endif
   <-numOfCoresWithSamePortalID 0 do
      ->unitUID
      <-unitUID self neq if
         <-unitUID ->partner
      endif
   loop
   <-partner CONST_COORDX GetUnitAttribute ->partnerX
   <-partner CONST_COORDY GetUnitAttribute ->partnerY
endonce

:destroyed
   <-partner 2 Destroy
[close]
Running on Ubuntu 12.04 LTS
Thanks in advance!

eduran

Looks like a bug to me. I assume the second core to go down tries to destroy the first one, which is already dead. Usually, calling 'Destroy' on a dead unit does nothing. I'd suggest you place a bug report with a link to this thread in the support forum (or have a mod move the entire thing there).

In the mean time, you can work around the issue by replacing the :destroyed function with something like this:
<-partner CONST_ISDESTROYED GetUnitAttribute if
Self 2 Destroy
endif

Grauniad

Attach a sample map with the scripts - the same map that cause your game to crash  - to this thread.

Also, if you run it again and it crashes again, please look in Creeperworld3 directory (where keydata.dat is kept - not the game code or the maps, another location), for log.txt and attach it to a post here.
A goodnight to all and to all a good night - Goodnight Moon

knucracker

Yeah, that definitely creates an infinite call loop.  The :Destroyed function gets called before the destroyed state is set.  Both the master and slave try to destroy their partner.  So master dies, which kills the slave, which kills the master, which kills the slave... etc.  (Until there is a stack overflow and the party crashes to a halt).

I'll see what I can do to prevent this...

LordBiscuit

First of all, thanks eduran for helping me work around this. My tower now works as intended :D

I tried running the map from Windows 7 on another machine.
It doesn't crash, but I can neither land on, build on, nor terraform the area where the cores used to be

Attached:
The .cw3 of my sample map
A screenshot of the weird thing I described on Windows 7

I could not find the log.txt file, tried to search within both of these:
~/Games/CreeperWorld3
~/Documents/CreeperWorld3

knucracker

The workaround doesn't actually work either... you are still getting a stack overflow problem which is creating a large number of stacked power zones (that's why they look funny in your screen shot, there are a gazillion of them stacked).

The only way to hack around this problem right now in your current build is to have each tower remember if it has already had its :destroyed called and to do nothing if it gets called again.
So in your :Destroyed function do something like this (untested, but this might work).

:destroyed
   if (<-destroying) return
   TRUE ->destroying
   #the rest of your destroy function

Grayzzur

Or maybe don't call Destroy on your partner if it's already flagged as destroyed:

:destroyed
  <-partner CONST_ISDESTROYED GetUnitAttribute not if
    <-partner 2 Destroy
  endif
"Fate. It protects fools, little children, and ships named 'Enterprise.'" -William T. Riker

Relli

Quote from: Grayzzur on January 24, 2014, 12:26:14 PM
Or maybe don't call Destroy on your partner if it's already flagged as destroyed:

:destroyed
  <-partner CONST_ISDESTROYED GetUnitAttribute not if
    <-partner 2 Destroy
  endif


I don't think this would work either.

Quote from: virgilw on January 24, 2014, 10:31:31 AM
The :Destroyed function gets called before the destroyed state is set.

So Core 1 dies, sees that its partner is alive, says "I'll fix that!", and destroys it. Trigger Core 2 dying, and it would likely read that Core 1 is still alive, since it hasn't had time to set itself to destroyed yet. Queue infinite loop yet again.

What if you built in a delay? If it gives itself time to change over to Destroyed status, it should work. And if the delay is small enough, it shouldn't interfere too much with the aesthetic. I just...don't know the right place to put said delay.

Clean0nion

It will get destroyed after the function has finished doing whatever it does.

Grayzzur

In my experience, that last tick in which it exists (and runs its :destroyed function), CONST_ISDESTROYED is set to true. Next frame, it doesn't exist at all.
"Fate. It protects fools, little children, and ships named 'Enterprise.'" -William T. Riker

LordBiscuit

Quote from: virgilw on January 24, 2014, 11:26:40 AM
The workaround doesn't actually work either... you are still getting a stack overflow problem which is creating a large number of stacked power zones (that's why they look funny in your screen shot, there are a gazillion of them stacked).

The only way to hack around this problem right now in your current build is to have each tower remember if it has already had its :destroyed called and to do nothing if it gets called again.
So in your :Destroyed function do something like this (untested, but this might work).

:destroyed
   if (<-destroying) return
   TRUE ->destroying
   #the rest of your destroy function
I didn't exactly use eduran's suggestion. What I did use is as follows:
The slave polls the master to see if it's destroyed, and destroys itself if it is. Then in its destruction function it checks if the master is still alive and kills it if it is. If the master dying triggered the slave to self destruct, the ISDESTROYED flag on the master would be true and it won't get destroyed again. If the slave died first, it would kill the master but won't continue to poll, so it won't self destruct again and cause the loop.
So code wise, the master deals only with transferring creep between the portals, and the slave only with making them both go down together.

knucracker

Btw, you shouldn't have to do anything special in the latest beta build.  You original solution should work fine.