summaryrefslogtreecommitdiff
path: root/src/main/smp/sys/time/Timers.smp
blob: eca32d15a801300bba72187d304bb13ce68a3107 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// Cooperative timers. poll returns IDs; callbacks never run on another thread.
// IDs are u32; 0 means failure or no timer due. Millisecond values use _ms.

// The


struct Timers {
    // Collection of timers (as struct-of-arrays)
    u32[] !_ids       = new u32[8]; // Which timer id for this slot
    i64[] !_due_ms    = new i64[8]; // Monotonic deadline in milliseconds
    i64[] !_period_ms = new i64[8]; // When does it repeat
    // Next available timer ID; 0 means all IDs have been used.
    u32 !_next = 1;

    // Add a new timer
    { i64 i64 i64 -> u32 } _add = { i64 current_ms, i64 delay_ms, i64 period_ms ->
        if( current_ms<0 || delay_ms<0 || current_ms>sys.integer.MAX_I64-delay_ms || _next==0 ) return 0;
        // Ugly linear scan for an available slot
        int slot=0;
        while( slot<_ids# && _ids[slot]!=0 ) slot++;
        // Double size for lots of timers
        if( slot==_ids# ) {
            if( _ids#>sys.integer.MAX_I32/2 ) return 0;
            val ids = new u32[_ids#*2];
            val due_ms = new i64[_ids#*2], periods_ms = new i64[_ids#*2];
            for( int i=0; i<_ids#; i++ ) { ids[i]=_ids[i]; due_ms[i]=_due_ms[i]; periods_ms[i]=_period_ms[i]; }
            _ids=ids; _due_ms=due_ms; _period_ms=periods_ms;
            // Retry against the newly published arrays after growing.
            return _add(current_ms,delay_ms,period_ms);
        }
        // Next available timer id
        u32 id = _next;
        _next = id==sys.integer.MAX_U32 ? 0 : id+1;
        _ids[slot]=id; _due_ms[slot]=current_ms+delay_ms; _period_ms[slot]=period_ms;
        return id;
    };

    // Explicit-clock versions make game logic and tests deterministic.
    { i64 i64 -> u32 } afterAt = { i64 current_ms, i64 delay_ms -> _add(current_ms,delay_ms,0); };
    { i64 i64 -> u32 } everyAt = { i64 current_ms, i64 period_ms -> period_ms<=0 ? 0 : _add(current_ms,period_ms,period_ms); };
    { i64 -> u32 } after = { i64 delay_ms  -> afterAt(time.monotonic(),delay_ms ); };
    { i64 -> u32 } every = { i64 period_ms -> everyAt(time.monotonic(),period_ms); };

    // Remove a timer
    val cancel = { u32 id ->
        if( id==0 ) return false;
        for( int i=0; i<_ids#; i++ ) if( _ids[i]==id ) { _ids[i]=0; return true; }
        return false;
    };

    // Earliest absolute monotonic deadline in milliseconds, or -1 when empty.
    val nextDeadline = { ->
        i64 next_ms = -1;
        // Ugly linear scan again, but short
        for( int i=0; i<_ids#; i++ )
            if( _ids[i]!=0 && (next_ms<0 || _due_ms[i] < next_ms) ) next_ms = _due_ms[i];
        return next_ms;
    };

    // Next firing timer.

    // Earliest deadline first; equal deadlines use registration order.
    // Repeats skip missed ticks and retain their phase, avoiding bursts/drift.
    { i64 -> u32 } poll = { i64 current_ms ->
        int slot = -1;
        for( int i=0; i<_ids#; i++ )
            if( _ids[i]!=0 && _due_ms[i]<=current_ms && (slot<0 || _due_ms[i]<_due_ms[slot] ||
                (_due_ms[i]==_due_ms[slot] && _ids[i]<_ids[slot])) ) slot=i;
        if( slot<0 ) return 0;
        u32 id = _ids[slot];
        i64 period_ms = _period_ms[slot];
        if( period_ms==0 ) _ids[slot]=0;
        else {
            i64 steps = (current_ms-_due_ms[slot])/period_ms;
            if( steps>=(sys.integer.MAX_I64-_due_ms[slot])/period_ms ) _ids[slot]=0;
            else _due_ms[slot] += (steps+1)*period_ms;
        }
        return id;
    };
};