PDA

View Full Version : [SOLVED] Timer is currently broken when it comes to Load and Undo



MrStump
01-23-2017, 08:22 PM
If a timer is running while you load or undo (or apply a new script), then it will continue to run despite that instance coming to an end. There is also no way to destroy it completely (onObjectDestroyed and onDestroy do not trigger on load/undo, I checked).

So, here is an example object script to highlight the problem. Here we are using a timer to reference back to itself. This can also be illustrated with a repetitions=0, but I'm using this method because I think it better highlights the issue. Example code:


function onload()
timerName = "a_special_snowflake_of_a_name"
--This does nothing to kill the timer from previous loops
Timer.destroy(timerName)

startTimerLoop()
end

function startTimerLoop()
--Without this, an error is thrown due to repeated names
--Considering the previous timer would have had to end before it can be
--created again, I do not see how this is possible.
Timer.destroy(timerName)

--[[
Timer.create({
identifier = timerName, function_name="loopingUpdate",
function_owner=self, delay=1
})
]]

print("Test")
end

function loopingUpdate()
--Stuff happens here
startTimerLoop()
end

With the timer.create commented out, this timer will not start. It prints Test once and ends. Now remove the commenting around Timer and save/apply the script. It will now print Test once per second. Now, comment out the timer.Create again and save/apply code. Notice that despite there not being a timer.create in the script, it is still printing Test over and over again. It will continue to do so. I believe disabling the function as a whole will cause the timer to throw an error.

If I was using repetition=0 on my timer instead of calling the starting function over and over, that error spam would continue over and over forever with no way to stop it.

SUGGESTED SOLUTION:
If possible, please just kill all co-routines and timers whenever any load/undo action is taken. If this is not possible, could we have an API trigger that goes off whenever a load/undo is activated. Something we could put a Timer.destroy inside of to end the timer before the next instance is laid on top of it?

Thank you

Sancho
01-23-2017, 11:31 PM
Here is the mod I made to demonstrate a lot of the problems with Load and Undo:

http://steamcommunity.com/sharedfiles/filedetails/?id=780958591

And I'll take the opportunity to plug my suggestion to give scripts control over autosaved undo states, since it's quite relevant:

http://www.berserk-games.com/forums/showthread.php?3430-Autosave-control

Mark
04-17-2017, 02:54 PM
I'm having trouble with this. Is there a way around it?

Sancho
04-23-2017, 11:30 AM
Huffel mentioned this in the old thread I made about this problem:


Timer.destroy() in the onDestroy() event should take care of that I think.

I don't know if I ever tried it.

Mark
04-23-2017, 01:41 PM
I put the Timer in a cube and made it remove the timer onDestroy(). It still produces loads of errors until the amount of repetitions runs out when I reload the mod. So that doesn't fix it. :*(

Huffel
04-23-2017, 03:36 PM
I tested it just now, for me it still works to delete the timer in the onDestroy() event. It is important to consider that the onDestroy events are only called for objects, not global:

Did you implement it in Global perhaps? It seems to work only for objects (which is strange for onObjectDestroyed). Alternatively you could implement it in the onLoad event as well to make sure there is no active Timer when the object is spawned.

My test code (code implemented in an object):

function onLoad(save_state)
local button_parameters = {}
button_parameters.click_function = 'startTimer'
button_parameters.function_owner = self
button_parameters.label = 'Start'
button_parameters.position = {0.0, 1.0, 0.0}
button_parameters.width = 2000
button_parameters.height = 1000
button_parameters.font_size = 100
self.createButton(button_parameters)
end

function startTimer()
Timer.create({identifier = 'testTimer', function_name = 'printsth', function_owner = self, delay = 3.0, repetitions = 12})
end
function printsth()
print('message')
end

function onDestroy()
Timer.destroy('testTimer')
end

Mark
04-23-2017, 04:55 PM
I must have messed something up then. I'll try again and see if I can get it to work. Thanks Huffel.

EDIT:



--TIMER CUBE SCRIPT.

scale = 2
myText = nil
timerID = "gameTimer"
playerTimers = {}
coloursPlaying = {}

function onLoad()
myText = getObjectFromGUID("8955f6")
if myText ~= nil then
myText:setScale({1,1,1})
myText:setScale({scale,scale,scale})

local myPos = self:getPosition()
myText:setPosition({myPos.x, myPos.y+0.1, myPos.z+2})

local myRot = self:getRotation()
myText:setRotation({myRot.x + 90, myRot.y - 90, myRot.z})
end
playerTimers = Global:getTable("playerTimers")
end

function onDestroy()
print("onDestroy() has been triggered.")
Timer.destroy(timerID)
end

function startTimer(info)
local seconds, function_name, useCountdown, needToBeDone = info[1], info[2], info[3], info[4]
if seconds == nil then print("ERROR: startTimer not set to an amount of seconds.") return end

Timer:destroy(timerID)

local timerP = {}
timerP.identifier = timerID
timerP.function_owner = Global
if useCountdown == true then
self:setScale({x=8, y=0.2, z=8})
myText:setValue(tostring(seconds))

coloursPlaying = Global:getTable("coloursPlaying")
for col, _ in pairs(coloursPlaying) do
playerTimers[col]:setValue(tostring(seconds))
end

timerP.function_name = "timerInterval"
timerP.delay = 1
timerP.repetitions = seconds
timerP.parameters = info
elseif type(function_name) == "table" then
timerP.function_name = "goToTable"
timerP.delay = seconds
else
timerP.function_name = function_name
timerP.delay = seconds
end

Timer:create(timerP)
end


Here's my code. When I manually delete it, it triggers the onDestroy() function but the timer doesn't stop. When I reload the mod, the onDestroy() function never triggers to remove the timer (there could just be a problem with print). I keep getting error messages after relogging with the timer still active.

I'm not sure what I'm doing wrong as the timer works as intended otherwise.

MrStump
04-23-2017, 08:01 PM
I had the same issue trying to use onDestroy remove the timer when the map is reloaded or changed entirely.

Huffel
04-24-2017, 11:53 AM
This is really strange. I played around with my example some more but I cannot get it to break.
I tested:

Putting the timer ID in a variable
Make the timer function throw an error instead of just a message
creating the timer from a coroutine

Everything works perfectly. The only thing I noticed: If I put a print in the onDestroy event there is no message when rewinding or reloading the save.

Have you tried to copy my example into an object and see if it will work for you?

Mark
04-24-2017, 01:48 PM
Your example works fine, I'm just trying to figure out what part of my code is different to cause it to behave this way.
I'm using Hotseat, but I also tried yours in Hotseat and there wasn't a problem.

I will update this post if I figure it out.

Knil
04-28-2017, 11:43 AM
I purposefully stop all events that trigger for a frame when resetting the Table to avoid people being able to spawn objects after the table has been cleared (and have a unkillable zombie scripting object).

Mark
05-03-2017, 06:18 PM
Hey Knil, thanks for the info. Here's the things you could 'fix' to solve this problem:

Timers should stop themselves automatically on a new load and shouldn't be allowed to continue.
If Timer.destroy() is called in the onLoad() function with the Timer's ID, the old Timer should actually get destroyed regardless of whether it was made in this instance.

Mark
05-25-2017, 02:51 PM
Thank you for fixing this in 9.2! :)