; _______________________________________________________________________________________
;|                                                                                       |
;| ..::FreshLib::..  Free, open source. Licensed under "BSD 2-clause" license."          |
;|_______________________________________________________________________________________|
;
;  Description: Asynchronous timer library.
;
;  Target OS: Linux
;
;  Dependencies:
;
;  Notes: Linux port of the library uses independent thread to manage timers.
;_________________________________________________________________________________________

__sys_time_slice = 100 ;[ms]


uglobal
  if used TimerCreate
    __InternalTimerID     dd ?
    __InternalTimerThread dd ?
    __FlagTimerTerminate  dd ?
  end if
endg



if used TimerCreate
  if ~(defined options.DebugMode.NoTimers & options.DebugMode.NoTimers)

initialize InitLinuxTimers
  .action lnx_sigaction
  .timer lnx_sigevent
  .time  lnx_itimerspec
begin
; set the signal handler.
        mov     eax, sys_sigaction
        mov     ebx, SIGALRM
        lea     ecx, [.action]
        mov     [ecx+lnx_sigaction.sa_handler], __TimerProc
        mov     [ecx+lnx_sigaction.sa_mask], 0
        mov     [ecx+lnx_sigaction.sa_flags], 0
        xor     edx, edx

        int $80

        DebugMsg 'Signal hadler set.'

; create the interval timer
        mov     [.timer.notify], SIGEV_SIGNAL
        mov     [.timer.signo], SIGALRM

        mov     eax, sys_timer_create
        mov     ebx, CLOCK_REALTIME
        lea     ecx, [.timer]
        mov     edx, __InternalTimerID

        int $80

        DebugMsg 'Interval timer created.'

; then start a thread that will process timer expiration events.

        stdcall ThreadCreate, __TimerHandler, 0

        OutputValue 'Timer thread created. ID=$', eax, 16, 8

        mov     [__InternalTimerThread], eax

; start the timer
        mov     ebx, [__InternalTimerID]
        mov     eax, sys_timer_settime
        lea     edx, [.time]
        mov     [edx+lnx_itimerspec.it_interval.tv_sec], 0
        mov     [edx+lnx_itimerspec.it_interval.tv_nsec], __sys_time_slice*1000000
        mov     [edx+lnx_itimerspec.it_value.tv_sec], 0
        mov     [edx+lnx_itimerspec.it_value.tv_nsec], __sys_time_slice*1000000
        xor     esi, esi
        int $80

        DebugMsg 'Timer started'

        return
endp




finalize FreeLinuxTimers
begin

; stop the timer
        mov     eax, sys_timer_delete
        mov     ebx, [__InternalTimerID]
        int $80

        DebugMsg 'Timer stopped.'


        mov     [__FlagTimerTerminate], 1

; signal the thread (because it is probably in waiting state.
match =native, LinuxThreads {
        mov     eax, sys_tkill
        mov     ebx, [__InternalTimerThread]
        mov     ecx, SIGRTMAX
        int     $80
}

match =posix, LinuxThreads {
        cinvoke pthread_kill, [__InternalTimerThread], SIGRTMAX
}

        cmp     [__InternalTimerThread], 0
        je      .finish

; waiting to timer thread to finish. This is bad implementation,
; here should be timer wait with signal terminate on timeout.

@@:
        stdcall Sleep, 10
        cmp     [__InternalTimerThread], 0
        jne     @b


.finish:
        DebugMsg 'Timer thread terminated.'
        return
endp
  end if
end if



uglobal
  __TimerEvent XClientMessageEvent
               rb 255
endg



; This procedure is called by the system on every time quantum.
; It is Linux signal handler, so the actions this procedure can
; perform are limited.

proc __TimerProc, .signal
.event XClientMessageEvent
begin
        push    ebx edi

        lea     eax, [__ptrFirstTimer]
        xor     edi, edi

.loop:
        mov     eax, [eax+TTimer.next]
        test    eax, eax
        jz      .end_timers

        test    [eax+TTimer.flags], tmfRunning
        jz      .loop

        mov     ecx, [eax+TTimer.value]
        add     ecx, __sys_time_slice

        mov     [eax+TTimer.value], ecx
        cmp     ecx, [eax+TTimer.interval]
        jl     .loop

@@:
        inc     [eax+TTimer.Expired]
        inc     edi

        mov     ecx, [eax+TTimer.interval]
        sub     [eax+TTimer.value], ecx
        cmp     [eax+TTimer.value], ecx
        jge     @b
        jmp     .loop


.end_timers:
        test    edi, edi
        jz      .exit

; signal the timers thread that some timer is changed.

match =native, LinuxThreads {
        mov     eax, sys_tkill
        mov     ebx, [__InternalTimerThread]
        mov     ecx, SIGRTMAX
        int     $80
}

match =posix, LinuxThreads {
        cinvoke pthread_kill, [__InternalTimerThread], SIGRTMAX
}

.exit:
        pop     edi ebx
        cret
endp



; This is thread procedure, that checks the timer expiration and then executes the timer callback procedure (if any)

proc __TimerHandler, .argument
.remain dd ?
.action lnx_sigaction
begin
        mov     eax, sys_sigaction
        mov     ebx, SIGRTMAX
        lea     ecx, [.action]
        mov     [ecx+lnx_sigaction.sa_handler], __continue_proc
        mov     [ecx+lnx_sigaction.sa_mask], 0
        mov     [ecx+lnx_sigaction.sa_flags], 0;SA_NODEFER
        xor     edx, edx
        int $80

        mov     [__FlagTimerTerminate], 0

.from_begin:

        cmp     [__FlagTimerTerminate], 0
        jne     .quit

        mov     [.remain], 0
        lea     eax, [__ptrFirstTimer]

.listloop:
        mov     eax, [eax+TTimer.next]

.listloop2:
        test    eax, eax
        jz      .end_list

        mov     ecx, [eax+TTimer.Expired]
        jecxz   .listloop

        dec     ecx
        mov     [eax+TTimer.Expired], ecx
        add     [.remain], ecx

        cmp     [eax+TTimer.Callback], 0
        je      .listloop

        mov     ecx, [eax+TTimer.flags]
        and     ecx, $0f
        cmp     ecx, tmfDoNothing
        je      .end_event

; call the callback procedure.
        pushad
        push    eax
        cinvoke XLockDisplay, [hApplicationDisplay]
        pop     eax
        stdcall [eax+TTimer.Callback], eax
        cinvoke XUnlockDisplay, [hApplicationDisplay]
        popad

.end_event:
        test    [eax+TTimer.flags], tmfSyncDestroy
        jz      .listloop

        push    eax
        mov     eax, [eax+TTimer.next]  ; after the destruction, this pointer will be lost.
        stdcall TimerDestroy ; pointer from the stack.
        jmp     .listloop2


.end_list:
        cmp     [.remain], 0
        jne     .from_begin

; wait for signal
        mov eax, sys_pause
        int $80
        jmp     .from_begin

.quit:
        mov     [__InternalTimerThread], 0

        stdcall Terminate, 0
endp



proc __continue_proc, .signal
begin
        cret
endp


; this procedure provides easy way to make time delays
; it is independent and does not lead to including whole timer library.

proc Sleep, .msec
.time lnx_timespec
begin
        pushad

        mov     eax, [.msec]
        xor     edx, edx
        mov     ecx, 1000
        div     ecx

        mov     [.time.tv_sec], eax     ; seconds
        imul    edx, 1000000
        mov     [.time.tv_nsec], edx

        mov     eax, sys_nanosleep
        lea     ebx, [.time]
        xor     ecx, ecx
        int $80

        popad
        return
endp




proc GetTime
begin
        push    ebx

        mov     eax, sys_time
        xor     ebx, ebx
        int     $80

        cdq
        pop     ebx
        return
endp



struct lnx_timeb
  .time    dd ?
  .millitm dd ?
  .timezone dd ?
  .dstflag  dd ?
ends


proc GetLocalTimeOffset
;.time lnx_timespec
begin
;        stdcall GetTime
;        push    eax
;
;        mov     eax, sys_clock_gettime
;        mov     ebx, CLOCK_REALTIME
;        lea     ecx, [.time]
;
;        int     $80
;
;
;        pop     edx
;        mov     eax, [.time.tv_sec]
;
;        OutputRegister regEAX, 10
;        OutputRegister regEDX, 10
;        int3

        xor     eax, eax
        return
endp