Statemachine vs. Thread
Posted: Thu Dec 29, 2022 5:23 pm
Hey folks!
Very often we see questions here about the surface no longer/not responding fast enough.
Simply writing, this is because the necessary main loop, in which all messages must/should be processed, is not called. (Also the use of BindEvent() needs a main loop.)
A frequently given answer here is called thread.
From my point of view there is also the possibility to split longer lasting tasks and to implement them via a so called statemachine in connection with a timer.
Like everything in life this approach has not only advantages but also some disadvantages. But of course this is also true for threads.
Here is a small windows app, which shows the usage of a Timer driven statemachine.
Bear in mind: I use the scan directory procedure as an example. (I know there are much better implementations around.)
I needed some long lasting job here...
You can abort the scan by pressing the "Abort" button any time.
HINT: Activate the filling of the gadget in slow motion by setting the #SLOW_MOTION to 1 ....
Very often we see questions here about the surface no longer/not responding fast enough.
Simply writing, this is because the necessary main loop, in which all messages must/should be processed, is not called. (Also the use of BindEvent() needs a main loop.)
A frequently given answer here is called thread.
From my point of view there is also the possibility to split longer lasting tasks and to implement them via a so called statemachine in connection with a timer.
Like everything in life this approach has not only advantages but also some disadvantages. But of course this is also true for threads.
Here is a small windows app, which shows the usage of a Timer driven statemachine.
Bear in mind: I use the scan directory procedure as an example. (I know there are much better implementations around.)
I needed some long lasting job here...
You can abort the scan by pressing the "Abort" button any time.
HINT: Activate the filling of the gadget in slow motion by setting the #SLOW_MOTION to 1 ....
Code: Select all
#SLOW_MOTION = 0 ; == 1 sets timer to 500 ms, very slow gadget filling
#MainCaption$ = "Test V0.00"
#Caption$ = "Test "
Enumeration EWindow
#WINDOW_Main
EndEnumeration
Enumeration EGadget
#GADGET_StrBaseDir
#GADGET_BtnUpdate
#GADGET_BtnAbort
#GADGET_LstFiles
EndEnumeration
Enumeration ETimer
#TIMER_Event
EndEnumeration
Enumeration EEvent #PB_Event_FirstCustomValue
#EVENT_UpdateUI
EndEnumeration
; ---------------------------------------------------------------------------------------------------------------------
Structure TFileNames
FileName$
Size.i ; could be .q but on Win64 it is the same.
DateCreated.i
DateModified.i
DateAccessed.i
EndStructure
;
Global NewList FileNames.TFileNames()
Procedure.i ScanDirectory(Gadget, Directory$, statemachine) ; Gloabl FileNames()
Static DIR = 0
Protected fn$, tmp$, ok
If statemachine = 0 ; ............................................. ; == start scanning file directory
DIR = ExamineDirectory(#PB_Any, Directory$, "*.*")
If DIR
ClearList(FileNames())
statemachine + 1 ; start state machine .. state == scan dir
EndIf
ElseIf statemachine = 1 ; ......................................... ; busy_state == scan file directory
If NextDirectoryEntry(DIR)
Select DirectoryEntryType(DIR)
; Case #PB_DirectoryEntry_Directory
; If Len(DirectoryEntryName(DIR)) > 2 ; more than C:
; ;do some on the subdirectory (not yet)
; EndIf
Case #PB_DirectoryEntry_File
Debug "TIMER statemachine = 1 .. found " + DirectoryEntryName(DIR)
AddElement(FileNames())
;FileNames()\FileName$ = Directory$ + DirectoryEntryName(DIR)
FileNames()\FileName$ = DirectoryEntryName(DIR)
FileNames()\Size = DirectoryEntrySize(DIR)
FileNames()\DateCreated = DirectoryEntryDate(DIR, #PB_Date_Created)
FileNames()\DateModified = DirectoryEntryDate(DIR, #PB_Date_Modified)
FileNames()\DateAccessed = DirectoryEntryDate(DIR, #PB_Date_Accessed)
EndSelect ;
Else ; NextDirectoryEntry(DIR)
Debug "TIMER statemachine = 1 .. last entry found "+#LF$
FinishDirectory(DIR)
DIR = 0
ResetList(FileNames())
ClearGadgetItems(Gadget)
statemachine + 1 ; do the next step
EndIf
ElseIf statemachine = 2 ; ......................................... ; == check on data
If NextElement(FileNames())
Debug "TIMER statemachine = 2 .. analyse '" + FileNames()\FileName$ + "'"
With FileNames()
AddGadgetItem(Gadget, -1, \FileName$ + #LF$ + \Size + #LF$ +
FormatDate("%yyyy.%mm.%dd %hh:%ii:%ss", \DateCreated) + #LF$ +
FormatDate("%yyyy.%mm.%dd %hh:%ii:%ss", \DateModified) + #LF$ +
FormatDate("%yyyy.%mm.%dd %hh:%ii:%ss", \DateAccessed))
EndWith
Else
; clean up properly
statemachine = 0 ; == done
EndIf ; NextElement(FileNames())
Else ; ............................................................ ; == reset state, unknown state (like user abort)
Debug "TIMER statemachine = reset "
If DIR <> 0
FinishDirectory(DIR)
DIR = 0
EndIf
statemachine = 0 ; reset state
EndIf
ProcedureReturn statemachine
EndProcedure
; ---== UI Section ==--------------------------------------------------------------------------------------------------
Procedure OpenMainWindow(WndW=800, WndH=600) ;
If OpenWindow(#WINDOW_Main, 8, 8, WndW, WndH, #MainCaption$, #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_ScreenCentered)
StickyWindow(#WINDOW_Main, 1) ; always on top, my sreen is a mess :)
StringGadget(#GADGET_StrBaseDir, 4, 4, WndW-92, 20, "<base path>")
GadgetToolTip(#GADGET_StrBaseDir, "Directory with JPEG files which includes Exif data ...")
ButtonGadget(#GADGET_BtnUpdate, WndW-84, 4, 80, 24, "Update")
GadgetToolTip(#GADGET_BtnUpdate, "Scan the directory for files and data ...")
ButtonGadget(#GADGET_BtnAbort, WndW-84, 4, 80, 24, "Abort")
GadgetToolTip(#GADGET_BtnAbort, "abort the running scan ...")
HideGadget(#GADGET_BtnAbort, 1) ; #GADGET_BtnAbort or #GADGET_BtnUpdate only one is visible :)
ListIconGadget(#GADGET_LstFiles, 4, 32, WndW-8, WndH-40, "FileName", 240, #PB_ListIcon_FullRowSelect | #PB_ListIcon_AlwaysShowSelection | #PB_ListIcon_GridLines)
AddGadgetColumn(#GADGET_LstFiles, 1, "Size", 120)
AddGadgetColumn(#GADGET_LstFiles, 2, "Created", 120)
AddGadgetColumn(#GADGET_LstFiles, 3, "Modified", 120)
AddGadgetColumn(#GADGET_LstFiles, 4, "Accessed", 120)
ProcedureReturn 1 ; success
EndIf
ProcedureReturn 0 ; failure
EndProcedure
; ---== main program ==------------------------------------------------------------------------------------------------
Procedure main()
Protected dir$, busy_state, tmp
Protected DIR
If OpenMainWindow()
busy_state = 0
dir$ = GetTemporaryDirectory()
SetGadgetText(#GADGET_StrBaseDir, dir$)
Repeat ; <--- main loop --->
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
Select EventWindow()
Case #WINDOW_Main
If busy_state = 0
Break ; say bye
Else
MessageRequester(#Caption$, "I am still busy!" + #LF$ + "Try again later...." , #PB_MessageRequester_Ok)
EndIf
; Case #WINDOW_View
; HideWindow(#WINDOW_View, 1)
EndSelect ; EventWindow()
Case #EVENT_UpdateUI ; my own event :)
tmp = EventData()
HideGadget(#GADGET_BtnUpdate, tmp)
HideGadget(#GADGET_BtnAbort, 1-tmp)
Case #PB_Event_Timer
Select EventTimer()
Case #TIMER_Event ; running the state machine by timer calls
busy_state = ScanDirectory(#GADGET_LstFiles, dir$, busy_state) ; busy_state changed inside procedure
If busy_state = 0
RemoveWindowTimer(#WINDOW_Main, #TIMER_Event)
PostEvent(#EVENT_UpdateUI, 0, 0, 0, 0)
EndIf
CompilerIf #SLOW_MOTION ; #PB_Compiler_Debugger ; ####
If busy_state = 2 ; second part of the statemachine (analysing) in slow motion :)
RemoveWindowTimer(#WINDOW_Main, #TIMER_Event)
AddWindowTimer(#WINDOW_Main, #TIMER_Event, 500) ; slower to deal with the program
EndIf
CompilerEndIf
EndSelect ; EventTimer()
Case #PB_Event_Gadget
Select EventGadget()
Case #GADGET_BtnUpdate
dir$ = GetGadgetText(#GADGET_StrBaseDir)
busy_state = ScanDirectory(#GADGET_LstFiles, dir$, 0) ; start, busy_state changed inside procedure
If busy_state
AddWindowTimer(#WINDOW_Main, #TIMER_Event, #USER_TIMER_MINIMUM) ; as fast as we can :) == 10 ms
EndIf
ClearGadgetItems(#GADGET_LstFiles)
AddGadgetItem(#GADGET_LstFiles, -1, "Scanning... ")
PostEvent(#EVENT_UpdateUI, 0, 0, 0, 1)
Case #GADGET_BtnAbort :Debug "abort the Update of the Image File List ... "
If busy_state
busy_state = -1 ; end the statemachine by next timer call :)
EndIf
AddGadgetItem(#GADGET_LstFiles, -1, "User Abort")
PostEvent(#EVENT_UpdateUI, 0, 0, 0, 0)
EndSelect
EndSelect
ForEver
RemoveWindowTimer(#WINDOW_Main, #TIMER_Event)
EndIf
ProcedureReturn 0
EndProcedure
End main()