EasyTimer

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
cxAlex
Beiträge: 2111
Registriert: 26.06.2008 10:42

EasyTimer

Beitrag von cxAlex »

Easy Timer ist ein kleines Timerbeispiel das leicht erweitert werden kann und an eure Bedürfnisse angepasst werden kann.

Konkret kann man damit eine Procedure alle x - Millisekunden im Hintergrund aufrufen lassen.

Code: Alles auswählen

; ------------------------------------------------------------------------------------
; Easy Timer
; Einfacher leicht erweiterbarer Timer
; PB 4.3 +
; Alexander Aigner
; V 1.0
; ------------------------------------------------------------------------------------

; ------------------------------------------------------------------------------------
; Internes Zeugs
; ------------------------------------------------------------------------------------

; ------------------------------------------------------------------------------------
; HighRes - Timer
; ------------------------------------------------------------------------------------
EnableExplicit

Prototype HR_Time()

Structure _HR_Internal
  StartTime.q
  Res.d
EndStructure

Define _HR._HR_Internal
Global GetTimeMS.HR_Time

Procedure _HR_InitTime()
  Shared _HR._HR_Internal
  Protected Frequency.q
  With _HR
    If Not QueryPerformanceFrequency_(@Frequency)
      ProcedureReturn #False
    Else
      QueryPerformanceCounter_(@\StartTime)
      \Res = 1000/Frequency
      ProcedureReturn #True
    EndIf
  EndWith
EndProcedure

Procedure _HR_Get()
  Shared _HR._HR_Internal
  Protected currentTime.q
  With _HR
    QueryPerformanceCounter_(@currentTime)
    ProcedureReturn (currentTime-\StartTime)*\Res
  EndWith
EndProcedure

Procedure _HR_oldPC_or_Linux()
  ProcedureReturn ElapsedMilliseconds()
EndProcedure

; Initialisierung des High-Res Timers

CompilerIf #PB_Compiler_OS<>#PB_OS_Windows
  GetTimeMS = @_HR_oldPC_or_Linux()
CompilerElse
  If _HR_InitTime()
    GetTimeMS = @_HR_Get()
  Else
    GetTimeMS = @_HR_oldPC_or_Linux()
  EndIf
CompilerEndIf

; ------------------------------------------------------------------------------------
; Eigentlicher Timer
; ------------------------------------------------------------------------------------

Prototype ET_Procedure(Event, UserData)

Enumeration ; Sollte klar sein ...
  #ET_Call
  #ET_Pause
  #ET_Resume
  #ET_Free
EndEnumeration

Structure ET_Data
  *CB.ET_Procedure
  UserData.i
  Delay.i
  CBEvent.i
  CBEventStop.i
  CBEventPause.i
EndStructure

Procedure _ET_Thread(*ET.ET_Data)
  Protected cTime, wTime
  With *ET
    wTime = \Delay
    Repeat
      
      If \CBEvent ; Nur 1. If-Abfrage für (später) beliebie viele Events
        If \CBEventPause ; Pausesignal
          \CB(#ET_Pause, \UserData)
          WaitSemaphore(\CBEventPause)
          \CB(#ET_Resume, \UserData)
        EndIf
        If \CBEventStop ; Stopsignal
          \CB(#ET_Free, \UserData)
          SignalSemaphore(\CBEventStop)
          ProcedureReturn #Null
        EndIf
        \CBEvent = #False
      EndIf
      
      cTime = GetTimeMS()
      While wTime>GetTimeMS()-cTime
        Delay(1)
        If \CBEvent ; Nur 1. If-Abfrage für (später) beliebie viele Events
          If \CBEventPause ; Pausesignal
            \CB(#ET_Pause, \UserData)
            WaitSemaphore(\CBEventPause)
            \CB(#ET_Resume, \UserData)
          EndIf
          If \CBEventStop ; Stopsignal
            \CB(#ET_Free, \UserData)
            SignalSemaphore(\CBEventStop)
            ProcedureReturn #Null
          EndIf
          \CBEvent = #False
        EndIf
      Wend
      
      ; Callback aufrufen und Ausgleichszeit errechnen
      cTime = GetTimeMS()
      \CB(#ET_Call, \UserData)
      wTime = \Delay-GetTimeMS() + cTime
      
      ; Delay(<0) = warte unendlich
      If wTime<0
        wTime = #Null
      EndIf
      
    ForEver
  EndWith
EndProcedure

; ------------------------------------------------------------------------------------
; Proceduren
; ------------------------------------------------------------------------------------

; Neuen Timer anlegen
Procedure ET_New(*Callback.ET_Procedure, Delay.i, UserData.i)
  Protected *ET.ET_Data = AllocateMemory(SizeOf(ET_Data))
  With *ET
    \CB = *Callback
    \Delay = Delay
    \UserData = UserData
    CreateThread(@_ET_Thread(), *ET)
    ProcedureReturn *ET
  EndWith
EndProcedure

; Timer pausieren
Procedure ET_Pause(*ET.ET_Data)
  With *ET
    If Not \CBEventPause
      \CBEventPause = CreateSemaphore()
      \CBEvent = #True
    EndIf
  EndWith
EndProcedure

; Timer fortsetzen
Procedure ET_Resume(*ET.ET_Data)
  With *ET
    If \CBEventPause
      SignalSemaphore(\CBEventPause)
      FreeSemaphore(\CBEventPause)
    EndIf
  EndWith
EndProcedure

; Timer freigeben
Procedure ET_Free(*ET.ET_Data)
  With *ET
    \CBEventStop = CreateSemaphore()
    \CBEvent = #True
    ET_Resume(*ET) ; Wenn nötig fortsetzen
    WaitSemaphore(\CBEventStop)
    FreeSemaphore(\CBEventStop)
    FreeMemory(*ET)
  EndWith
EndProcedure

DisableExplicit

; ------------------------------------------------------------------------------------
; Test
; ------------------------------------------------------------------------------------

Procedure Test(Event, Dummy)
  Static x, lT, cT
  If Event = #ET_Call
    x + 1
    cT = GetTimeMs()
    If lT
      PrintN("Getriggert nach " + Str(cT-lT) + " ms " + Str(x))
    Else
      PrintN("Timer Initialisiert " + Str(x))
    EndIf
    Delay(Random(50))
    lT = ct
  ElseIf Event = #ET_Free
    PrintN("Timer freigegeben ")
  EndIf
  ;Debug x
EndProcedure

; Test alle 100 ms aufrufen

OpenConsole()

Timer1 = ET_New(@Test(), 50, #Null)
Delay(1020)
; Timer freigeben
ET_Free(Timer1)

Input()
CloseConsole()
Gruß, Alex
Zuletzt geändert von cxAlex am 06.07.2009 13:19, insgesamt 6-mal geändert.
Projekte: IO.pbi, vcpu
Pausierte Projekte: Easy Network Manager, µC Emulator
Aufgegebene Projekte: ECluster

Bild

PB 5.1 x64/x86; OS: Win7 x64/Ubuntu 10.x x86
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

dann schreib dem "Job" mal ne messitch, damit es das auch findet...
Der Narr denkt er sei ein weiser Mann.
Der Weise weiß, dass er ein Narr ist.
Benutzeravatar
cxAlex
Beiträge: 2111
Registriert: 26.06.2008 10:42

Beitrag von cxAlex »

Schon passiert...
Projekte: IO.pbi, vcpu
Pausierte Projekte: Easy Network Manager, µC Emulator
Aufgegebene Projekte: ECluster

Bild

PB 5.1 x64/x86; OS: Win7 x64/Ubuntu 10.x x86
Andesdaf
Moderator
Beiträge: 2671
Registriert: 15.06.2008 18:22
Wohnort: Dresden

Beitrag von Andesdaf »

dankeschön für den codeh :D
Win11 x64 | PB 6.20
Benutzeravatar
cxAlex
Beiträge: 2111
Registriert: 26.06.2008 10:42

Beitrag von cxAlex »

Update:
  • Timer kann pausiert/fortgesetzt werden
  • Kleiner BugFix (Delay(<0) = Unendlich warten, sollte vlt. in die Doku...)
Code siehe 1. Post

Gruß, Alex
Projekte: IO.pbi, vcpu
Pausierte Projekte: Easy Network Manager, µC Emulator
Aufgegebene Projekte: ECluster

Bild

PB 5.1 x64/x86; OS: Win7 x64/Ubuntu 10.x x86
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8807
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Beitrag von NicTheQuick »

Ich finde das mit dem Delay ein bisschen schlecht gelöst. Besser finde ich es überhaupt
kein Delay zu nutzen und stattdessen nur mit 'ElapsedMilliseconds()' zu arbeiten. Bei dem
Delay kann sich nämlich wegen der geringen Ungenauigkeit eine große Ungenauigkeit
ansammeln!

Code: Alles auswählen

Wir nehmen als Timerwert mal 100 ms.

ideal  bisher  besser   
0      0       0        Timer initialisiert
100    105     105      ausgelöst
200    212     207      ausgelöst
300    320     308      ausgelöst
400    426     406      ausgelöst
500    530     504      ausgelöst
Benutzeravatar
cxAlex
Beiträge: 2111
Registriert: 26.06.2008 10:42

Beitrag von cxAlex »

Habs mal verbessert, bei mir ist die max. Ungenauigkeit jetzt 1 ms:

Code siehe oben.
Debugger hat geschrieben:Timer Initialisiert
Getriggert nach 100 ms
Getriggert nach 101 ms
Getriggert nach 100 ms
Getriggert nach 101 ms
Getriggert nach 100 ms
Getriggert nach 100 ms
Getriggert nach 101 ms
Getriggert nach 100 ms
Getriggert nach 101 ms
Projekte: IO.pbi, vcpu
Pausierte Projekte: Easy Network Manager, µC Emulator
Aufgegebene Projekte: ECluster

Bild

PB 5.1 x64/x86; OS: Win7 x64/Ubuntu 10.x x86
Benutzeravatar
cxAlex
Beiträge: 2111
Registriert: 26.06.2008 10:42

Beitrag von cxAlex »

Kleines Update.

Code nochmal leicht überarbeitet, genauer gehts nicht. (Außer man macht lastet die CPU resp. 1. Kern beim Warten mal eben 100% aus :mrgreen:).

Im Demo sieht man jetzt schön wie der Timer die Ausführungsdauer der Procedure ausgleicht.

Gruß, Alex
Projekte: IO.pbi, vcpu
Pausierte Projekte: Easy Network Manager, µC Emulator
Aufgegebene Projekte: ECluster

Bild

PB 5.1 x64/x86; OS: Win7 x64/Ubuntu 10.x x86
Andesdaf
Moderator
Beiträge: 2671
Registriert: 15.06.2008 18:22
Wohnort: Dresden

Beitrag von Andesdaf »

schön. Gleich wird geupdatet.
Win11 x64 | PB 6.20
Benutzeravatar
mk-soft
Beiträge: 3845
Registriert: 24.11.2004 13:12
Wohnort: Germany

Beitrag von mk-soft »

Hi,

Schöne Arbeit

Deine letzte version funktionierte bei kleinen Delay besser. Versuch es mal mit 10ms

Code: Alles auswählen

      wTime = \Delay - dTime
      cTime = GetTimeMS()
      While wTime > GetTimeMS()-cTime
        Delay(1)
      Wend
Delay(1) (WinAPI Sleep) kehrt wohl sofort zurück und entlastet nur die CPU um die bearbeitung andere Thread zu gewährleisten
Delay(>1) wird sehr ungenau.

Tipp: Vergib bitte eine Versionsnummer

Vielleicht die Eventkennung su umbauen das man in der eigenen Thread noch den Pause und Free abfragen kann um noch wichtige verarbeitungen
durchführen kann

FF :wink:
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Antworten