1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  4. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  5. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  6. The results are out! Check them out.
    Dismiss Notice
  7. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  8. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  9. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Imprecision of timers and working around it (Lua)

Discussion in 'World Editor Help Zone' started by Uncle, Jul 24, 2019.

  1. Uncle

    Uncle

    Joined:
    Aug 10, 2018
    Messages:
    487
    Resources:
    0
    Resources:
    0
    The problem is that the Reals aren't precise. Here's a simple code I made in Lua:
    Code (Lua):

    local duration = 5.00

    TimerStart(SomeTimer, 0.05, true, function()
        duration = duration - 0.05
        print("Duration = ", duration)
        if duration <= 0 then
            --End the timer
        end
    end
    end)
     
    When I print out duration it starts out precise displaying "Duration = 5.00, 4.95, 4.90, etc..." but eventually it starts to lose precision and I begin to see "Duration = 3.999" instead of "Duration = 4.0". This is a problem if say I wanted to check when duration was equal to exactly "0.50". So how exactly would you recommend working around this?

    Code (Lua):

    local counter = 100

    TimerStart(SomeTimer, 0.05, true, function()
        counter = counter - 1
        print("Duration = ", counter)
        if counter == 0 then
            --End the timer
        end
    end
    end)
     
    I know I can rely on Integers like in the example above but I'd rather not do this since it complicates things (especially if I want the duration to change depending on different variables).

    Am I missing something? Can I get precise Reals or am I stuck with using <= and booleans and all sorts of weird tricks to check how much time has elapsed in my repeating timer?
     
    Last edited: Jul 24, 2019
  2. The_Silent

    The_Silent

    Joined:
    Feb 4, 2008
    Messages:
    2,891
    Resources:
    159
    Models:
    49
    Icons:
    89
    Packs:
    8
    Skins:
    12
    Maps:
    1
    Resources:
    159
    Looks like floating point errors. Not sure what the best way to get around those in wc3 lua. Usually I would just ignore them, as they are usually so small they are negligible.
    Solutions for floating point rounding errors
     
  3. Dr Super Good

    Dr Super Good

    Spell Reviewer

    Joined:
    Jan 18, 2005
    Messages:
    25,590
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    The solution is to not use reals to meter time. Either use integers to meter ticks/cycles or use timer callbacks which run after the full duration.

    Reals are 32 bit software emulated floating point numbers. They are subject to floating point error like all floating point numbers. Nothing stops one from implementing fixed point numbers using integers to meter fractional time in a way that is not subject to floating point error.
     
  4. WaterKnight

    WaterKnight

    Joined:
    Aug 18, 2009
    Messages:
    4,033
    Resources:
    5
    Maps:
    1
    Tutorials:
    4
    Resources:
    5
    I often used a separate timer for the duration since this also has the nice effect of decoupling the two things but it depends really. If you want a fixed amount of intervals, better ensure that explicitly with integer ticks.
     
  5. Ceday

    Ceday

    Joined:
    Feb 22, 2010
    Messages:
    1,077
    Resources:
    0
    Resources:
    0
    Never accumulate real values like that. When I am making spells with some kind of duration using timers, I calculate how many timer ticks will occur first and store that value in an integer variable. For example period of your timer is 0.05 and duration of your spell is calculated as 3.42. This means 68 ticks will occur. I accumulate elapsed ticks in timer callback and finish the timer when it reaches 68.
     
  6. Uncle

    Uncle

    Joined:
    Aug 10, 2018
    Messages:
    487
    Resources:
    0
    Resources:
    0
    Thanks for the replies. I'll probably end up using two timers for some things.
    Anyway, I realized I could still use Integers and achieve what I was trying to do:

    A Damage over Time ability with a Duration that differs based on the casting unit's "Skill Duration" stat. As seen in a lot of games like Path of Exile.
    Code (Lua):

    SkillDuration[source] = GetRandomReal(1.0, 2.0)

    function Ignite()
        --Configure
        local source = GetTriggerUnit()
        local target = GetSpellTargetUnit()
        local duration = 5.00 * SkillDuration[source]
        local totaldamage = 300.00 * SkillDuration[source]
        local intervals = 10

        local timerinterval = duration / intervals
        local damage = totaldamage / intervals

        TimerStart(CreateTimer(), timerinterval, true, function()
            intervals = intervals - 1
            UnitDamageTarget(source, target, damage, false, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, nil)
            if intervals == 0 then
                --destroy timer
            end
        end)    
    end
    end