Page 1 of 2
Level Vs Level
Posted: Fri Apr 11, 2014 7:30 am
by Mysterious
Just a question. For every level do the amount of objects, fx's, lights, monsters etc.... affect the next level? I mean performance wise.. Say lvl 1 has not many things in it as is lvl2. Then as you progress to next lot of levels and add more objects does this start to slow the game down??
I am just wondering is this true?
Ohh thxs Orrr2 guys for your Source Code, although it's hard to understand (for me that is) it's a great help

Re: Level Vs Level
Posted: Fri Apr 11, 2014 9:10 am
by antti
Having a level with A LOT of stuff in it can affect performance, even if you are not in that level since the other levels are running (just the simulation, not the graphics) regardless of if you are in the level or not. But generally speaking, the CPU load of Grimrock 1 was quite light and not the bottleneck (the game was limited by the GPU pretty much always) so the threat feels quite theoretical to me.
To sum it up, I really wouldn't worry about the other levels bogging down the performance.
Re: Level Vs Level
Posted: Fri Apr 11, 2014 10:17 am
by msyblade
Some easily avoidable things that can slowdown performance in a mod:
Multiple FX's/lights visible: Try to limit the torches/FX's visible to the party, to 3 or 4 at any given time.
Hooks: Some hooks require constant checks. The ORRR2 is a good example of this. For instance, if you have an onMove hook (or three!), it has to check every time the party moves a square. Add to this a monster move hook, and it has to check everytime that monster moves also (and possibly other monsters, each time they move), it can start to add up.
Timers: Deactivate timers when they finish their task. Some dungeons have dozens upon dozens of timers doing many different things. To maximize effeciency, always ensure that they deactivate themselves when they're done. Timer based script checks are a terrible bleed on resources also. For example, a script checking for a projectile every 0.1 seconds, throughout an entire dungeon, just to cover 1 square near a particular puzzle would be a bad idea. Start the timer checks when the player gets near, and deactivate when not needed anymore. Or find another way to accomplish the goal.
It's also noteworthy that timers hit every frame on the current level, but only every other frame on the levels above/below, and even less on the levels further away. This can throw multilevel puzzles off balance. It also means low framerate can affect the timers in slight ways.
As Antti says, the real bottleneck comes from the GPU, so the first culprit to tackle is visible lights/FX . The timers and hooks really have to be numerous to cause slowdown, but it doesn't take many Torches before you experience a performance drop.
In conclusion, the number of assets doesn't have much of an effect, unless you are running constant checks on many different things (As is the case in the ORRR2). This is why Diarmuid set it up to load each room on entry, and unload on exit. Once you start to affect framerate, every tick counts.
Hope this helps!
Re: Level Vs Level
Posted: Mon Apr 14, 2014 10:18 am
by Mysterious
Hi guys. Sorry for answering so late Computer went down.
Ok thank you for that info, will keep this in mind. I have noticed things are different with Timers. I don't know if I am correct here but they are time wise different on each level. It could just be me but not sure. It feels as though they get slower each level. Aren't they supposed to be the same, I mean a timer is a timer eg: 1 to 60 Seconds so they should be all the same? I think that's what you mentioned Mysblade.
Mysblade thxs for the additional Village objects that go with the Set

and for your answer Antti

Re: Level Vs Level
Posted: Mon Apr 14, 2014 11:57 am
by antti
Mysterious wrote:Hi guys. Sorry for answering so late Computer went down.
Ok thank you for that info, will keep this in mind. I have noticed things are different with Timers. I don't know if I am correct here but they are time wise different on each level. It could just be me but not sure. It feels as though they get slower each level. Aren't they supposed to be the same, I mean a timer is a timer eg: 1 to 60 Seconds so they should be all the same? I think that's what you mentioned Mysblade.
Mysblade thxs for the additional Village objects that go with the Set

and for your answer Antti

The other levels are updated slower so the timers also advance slower there. To avoid unnecessary load on CPU, during each frame only three levels are updated and the logic goes like this:
1. The current level player party is on is updated every frame
2. The level below and above take turns updating. Eg, every even frame, the level above is updated and every odd frame, the level below is updated.
3. All the rest of the levels are updated in round robin fashion, eg. they take turns after each other. Basically this means that if you have 103 levels in your dungeon, it might take 100 frames until a specific level is updated.
If you have timing critical scripts that need to work across multiple levels, I remember that people here have used timers in every level and I'm sure someone here can provide you with the implementation details

Also in some cases, using getStatistic("play_time") could be suitable for keeping things in sync.
Re: Level Vs Level
Posted: Mon Apr 14, 2014 3:25 pm
by msyblade
The solution for the timer issue by guys way smarter than me: Spawn dynamic timers, as needed, using the play time stat to get reference and set . Or pass a timer from one floor to the next, destroying the previous, creating a new one on current floor, and applying the math to subtract what is needed from the count.
I THINK Komag came up with a more elegant solution, although i never investigated it fully.
Re: Level Vs Level
Posted: Mon Apr 14, 2014 5:17 pm
by JohnWordsworth
If you need something that's ticking often (but not necessarily every single frame) then a good solution is something like this. I'm just writing this off the top of my head, so it might need some tweaks. But create a script_entity called "global_timers" and use the following script...
Global Timer Script: global_timers
Code: Select all
gGlobalTimers = {};
gTimerToGlobalMap = {};
--
-- Create a global timer that will tick at the same rate regardless of the level the player is on.
-- Note: The timer is running by default but can be stopped (paused) or destroyed later.
--
function createTimer(id, interval, tickFunc)
if ( gGlobalTimers[id] ~= nil ) then
print("A timer with ID <" .. id .. "> already exists, cannot create another.");
end
local levelCount = getMaxLevels();
local globalTimerInfo = {
timerId = id,
timers = {},
lastTick = getStatistic("play_time"),
isRunning = true,
tickFuncs = {},
destroyFuncs = {},
destroyDelay = 0,
};
if ( type(tickFunc) == "function" ) then
table.insert(globalTimerInfo.tickFuncs, tickFunc);
elseif ( type(tickFunc) == "table" ) then
globalTimerInfo.tickFuncs = tickFunc;
end
for lvl = 1, levelCount do
local timer_id = "gt_" .. id .. "_" .. lvl;
local timer = spawn("timer", lvl, 1, 1, 1, timer_id);
timer:setTimerInterval(interval);
timer:addConnector("activate", "global_timers", "tick");
timer:activate();
table.insert(globalTimerInfo["timers"], timer_id);
gTimerToGlobalMap[timer_id] = id;
end
gGlobalTimers[id] = globalTimerInfo;
end
--
-- Add a tick function to a global timer
--
function addTimerTickFunc(id, tickFunc)
if ( gGlobalTimers[id] == nil ) then
print("Cannot add tick func to global timer " .. id .. " without creating it first");
return false;
end
if ( type(tickFunc) ~= "function" ) then
print("Tried to add tick func to timer, but given " .. typeof(tickFunc) .. " instead.");
return false;
end
table.insert(gGlobalTimers[id].tickFuncs, tickFunc);
return true;
end
--
-- Add a destoy callback function to a global timer
--
function addTimerDestroyFunc(id, destroyFunc)
if ( gGlobalTimers[id] == nil ) then
print("Cannot add destroy func to global timer " .. id .. " without creating it first");
return false;
end
if ( type(destroyFunc) ~= "function" ) then
print("Tried to add tick func to timer, but given " .. typeof(destroyFunc) .. " instead.");
return false;
end
table.insert(gGlobalTimers[id].destroyFuncs, destroyFunc);
return true;
end
--
-- Start a timer that has previously be paused.
--
function startTimer(id)
if ( gGlobalTimers[id] == nil ) then
print("Cannot start global timer " .. id .. " without creating it first");
return false;
end
gGlobalTimers[id]["isRunning"] = true;
return true;
end
--
-- Stop a running global timer but don't destroy it (so it can be resumed)
--
function stopTimer(id)
if ( gGlobalTimers[id] == nil ) then
print("Cannot stop global timer " .. id .. " without creating it first");
return false;
end
gGlobalTimers[id]["isRunning"] = false;
return true;
end
--
-- Destroy the given global timer, removing it completely from the world
--
function destroyTimer(id, delay)
if ( gGlobalTimers[id] == nil ) then
print("Cannot destroy global timer " .. id .. " without creating it first");
return false;
end
if ( delay ~= nil and delay > 0 ) then
gGlobalTimers[id].destroyDelay = delay;
return false;
end
for i,timer_id in ipairs(gGlobalTimers[id]["timers"]) do
local timer = findEntity(timer_id);
gTimerToGlobalMap[timer_id] = nil;
if ( timer ~= nil ) then
timer:destroy();
end
end
gGlobalTimers[id] = nil;
for i,func in ipairs(globalTimer.destroyFuncs) do
func(dt);
end
end
--
-- Internal 'tick' method for the timers
--
function tick(sender)
if ( sender.level ~= party.level ) then
return;
end
globalTimer = gGlobalTimers[gTimerToGlobalMap[sender.id]];
if ( globalTimer == nil ) then
return;
end
local time = getStatistic("play_time");
local dt = time - globalTimer.lastTick;
globalTimer.lastTick = time;
for i,func in ipairs(globalTimer.tickFuncs) do
func(dt);
end
if ( globalTimer.destroyDelay > 0 ) then
globalTimer.destroyDelay = globalTimer.destroyDelay - dt;
if ( globalTimer.destroyDelay <= 0 ) then
destroyTimer(globalTimer.timerId);
end
end
end
Hmm, this grew a bit bigger than I planned while writing it. However, assuming this works (untested at the moment), then you should just be able to do the following elsewhere in your dungeon...
Example Usage
Code: Select all
-- Sample script for using Global Timers
-- Note: This should really be called from a function instead of just being in a script
-- entity, otherwise it will only work if the global_timers script is loaded first.
function tickA(dt)
print("Timer 1 Tick A: " .. dt);
end
function tickB(dt)
print("Timer 1 Tick B: " .. dt);
end
if ( findEntity("global_timers") ~= nil ) then
-- Start timer
global_timers.createTimer("my_timer_id", 0.10);
global_timers.addTimerTickFunc("my_timer_id", tickA);
global_timers.addTimerTickFunc("my_timer_id", tickB);
global_timers.addTimerDestroyFunc("my_timer_id", function() print("Timer Destroyed!"); end);
-- Destroy in 5 seconds
global_timers.destroyTimer("my_timer_id", 5);
else
print("Global Timers not installed");
end
Let me know how you get on. If it doesn't work and you find the fix, please post it here and I'll update. If it doesn't work and you can't make it work, I'll paste it into a dungeon when I get home and fix it!
Edit: Updated with fixed code. This should just work now

Re: Level Vs Level
Posted: Mon Apr 14, 2014 9:18 pm
by JKos
Just happened to notice this thread and remembered from other thread that you use my framework. It already has a support for constant timers, basically the idea is same as JohnWordsworth's. The problem with that approach is that when party changes dungeon level the timer on the target level is running behind, so there will be gaps on level changes and this gap is growing with the interval. Which means that this approach only works well enough with really small intervals. My solution was that when the interval is over 1 second the timer will internally use 0.1 second interval and count to actual interval (see the code if you like) and also in my solution only the timer on the party level is active, which saves some cpu-cycles.
Usage example
Code: Select all
local t = timers:create('my_constant_timer')
t:setConstant(true) --this makes the timer constant
t:addConnector("activate", "script", "tick");
t:activate()
Timers-script also has a lot of other features, documentation:
https://sites.google.com/site/jkosgrimr ... ule-timers
ORRR2 uses a party:onDraw-hook and party:getStatistics('play_time') to calculate the intervals, I think that it is a better approach, but I wrote my script before we had onDraw-hook and it works well enough too for most cases.
Re: Level Vs Level
Posted: Tue Apr 15, 2014 3:47 am
by Mysterious
Hi guys.
First thank you all for your replies, it is good to have so much support in this community
I would appreciate a Demo Mod if possible John thank you, only if you have the time. I do enjoy a challenge but the Coding seems Alien to me
. If I could get a sample Dungeon then I should be able to work it out from there.
Jkos I will also give your system a try, I have been reading the Website on most of the Framework System and it's a slow process for me, being so new to lua coding. Well any coding for that fact.
Antti it is very good that you help the community out so much. I don't remember any other Software makers doing this

Well anyway thank you all for your replies again and cant wait to get the Timer running better. Also LOG 2 release.
PS: Yes I did look at the ORRR 2 Timer and Komags Timer, but to me it's like reading a different language

but it wont stop me from my goal.. Hey just learnt how to highlight text in the post lol. Have a nice day guys
EDIT: John I tried your code and it comes up with this error: '}' expected (to close '{' at line 16) near '='; I am not sure what this means.
Oh the line of Code is:
local levelCount = getMaxLevels();
local globalTimerInfo = {
"timers" = {}, -- This is where Error is --
"lastTick" = getStatistic("play_time"),
"isRunning" = true,
"tickFunction" = tickFunction
};
Re: Level Vs Level
Posted: Thu Apr 17, 2014 3:21 am
by JohnWordsworth
Hi Mysterious, I've popped my global timer script into a sample dungeon and fixed the bug. Have also made a few changes.
1. You can now add multiple callbacks for a single global timer to save resources. So, if you have a bunch of things that you would like to happen every 1/10th of a second - create one timer with a 0.1 interval and add multiple callbacks. You can also add a function callback for when the timer gets destroyed.
2. You can now also destroy a timer after a given amount of time. Just call 'global_timers.destroyTimer("timer_id", 5)' and it will get destroyed in 5 seconds.
You can grab it from here:
https://dl.dropboxusercontent.com/u/610 ... xample.zip
Installation is still almost the same as before - just add the global_timers script entity to your own mod and call functions on it. As mentioned by JKos, don't expect this to always callback at the same interval. Moving up and down levels will result in one call being off. However, your tick function receives a 'dt' parameter - which is the amount of time that has passed since the last call. Use this to keep track of the exact changes in time.
If you really need something to happen every single frame or very precisely at a repeated interval (even when changing floors), you'll have to look at an onGui hook solution. However, this is more effort than just dropping a script entity in as you also need to edit some lua files. I don't particularly like using onGui for anything but rendering or GUI related methods - but that's just me being finicky about putting game logic in the render loop - however, it is the most reliable high-resolution function call we get.
I'll update my previous post with the new script and info.