Delayed procedure calls (frame based)?

Just starting out? Need help? Post your questions and find answers here.
User avatar
spacesurfer
User
User
Posts: 15
Joined: Fri May 11, 2012 8:22 am
Location: Hrvatska

Delayed procedure calls (frame based)?

Post by spacesurfer »

I am looking at this code that can be used to delay procedure calls, seems this could be (with a slight modification) very useful in a frame based games - for example after clearing the stage in some shooting game we might want to schedule the following tasks:

- generate a sequence of time delayed explosions
- generate new enemies

Code: Select all

For n = 1 To 20
  RegisterTask(@create_explosion(), 10*n, #False)
Next
;new stage after 500 frames
RegisterTask(@create_new_enemies(), 500, #False)  ;5 s after the last explosion
The RunTasks() would then each time (each frame) decrease the delay variable and execute the call when delay (counter) would reach zero.

However, it would be nice to be able to call the procedures with variable parameters, something as this code, so I wonder if it is possible to implement something similar to JavaScript setTimeout() method? That would be some kind of FIFO stack for the procedure calls including the parameters.
User avatar
STARGÅTE
Addict
Addict
Posts: 2277
Joined: Thu Jan 10, 2008 1:30 pm
Location: Germany, Glienicke
Contact:

Re: Delayed procedure calls (frame based)?

Post by STARGÅTE »

You can use Threads, to call a procedure with a delay

Or you can use the WindowTimers like this:

Code: Select all

Enumeration
	#Window
	#Timer_NewEnemies
EndEnumeration


OpenWindow(#Window, 0, 0, 800, 600, "WindowTitle", #PB_Window_MinimizeGadget|#PB_Window_ScreenCentered)
	AddWindowTimer(#Window, #Timer_NewEnemies, 500)

Repeat
	
	Select WaitWindowEvent()
			
		Case #PB_Event_CloseWindow
			End
			
		Case #PB_Event_Timer
			Select EventTimer()
				Case #Timer_NewEnemies
					Debug "Call"
					; create_new_enemies()
					RemoveWindowTimer(#Window, #Timer_NewEnemies)
			EndSelect
			
	EndSelect
	
ForEver
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Lizard - Script language for symbolic calculations and moreTypeface - Sprite-based font include/module
User avatar
spacesurfer
User
User
Posts: 15
Joined: Fri May 11, 2012 8:22 am
Location: Hrvatska

Re: Delayed procedure calls (frame based)?

Post by spacesurfer »

STARGÅTE wrote:You can use Threads, to call a procedure with a delay
You mean to start time delay + a procedure call in a new thread? That would be a time delay. But frame delay would have to communicate with the main thread, every pass thru the main loop in the main thread would have to decrement the frame_delay counter in the waiting thread. Do you thing something like that could be done using the WaitSemaphore() and SignalSemaphore()?

Thanks for the code!
User avatar
STARGÅTE
Addict
Addict
Posts: 2277
Joined: Thu Jan 10, 2008 1:30 pm
Location: Germany, Glienicke
Contact:

Re: Delayed procedure calls (frame based)?

Post by STARGÅTE »

if you will call your procedures synchronous, you can use some like this:

CreateCall(Delay, Counts) create a element and ExamineCalls() returns the element which to call:

Code: Select all

Structure Call
	Delay.i
	Counts.i
	Time.i
EndStructure

Global NewList Call.Call()

Procedure CreateCall(Delay.i, Counts.i=1)
	AddElement(Call())
	Call()\Delay  = Delay
	Call()\Counts = Counts
	Call()\Time   = ElapsedMilliseconds()
	ProcedureReturn @Call()
EndProcedure

Procedure ExamineCalls()
	Protected Time.i = ElapsedMilliseconds()
	ForEach Call()
		If Call()\Time+Call()\Delay < Time And Call()\Counts
			Call()\Counts - 1
			Call()\Time   = Time 
			ProcedureReturn @Call()
		EndIf
	Next
EndProcedure

; Example

Define *explosion = CreateCall(500, 3)
Define *new_enemies = CreateCall(2500)

Repeat
	
	Select ExamineCalls()
		Case *explosion
			Debug "Call your explosion procedure"
		Case *new_enemies
			Debug "Call your new_enemies procedure"
		Default
			Delay(10) ; Frame
	EndSelect
	
ForEver
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Lizard - Script language for symbolic calculations and moreTypeface - Sprite-based font include/module
User avatar
spacesurfer
User
User
Posts: 15
Joined: Fri May 11, 2012 8:22 am
Location: Hrvatska

Re: Delayed procedure calls (frame based)?

Post by spacesurfer »

STARGÅTE wrote:if you will call your procedures synchronous, you can use some like this:
Thank you very much. I'll have to take a bit of time to examine the code you've sent (and to think a little about it) then I'll probably have an additional question or two...
User avatar
spacesurfer
User
User
Posts: 15
Joined: Fri May 11, 2012 8:22 am
Location: Hrvatska

Re: Delayed procedure calls (frame based)?

Post by spacesurfer »

STARGÅTE wrote:if you will call your procedures synchronous, you can use some like this:

CreateCall(Delay, Counts) create a element and ExamineCalls() returns the element which to call:
Your code works fine, hovewer this would be what I meant considering timings:

Code: Select all

Structure Call
  delay_frames.i
  Time.i
EndStructure

Global NewList Call.Call()

Procedure CreateCall(delay_frames.i)
  AddElement(Call())
  Call()\delay_frames = delay_frames
  ProcedureReturn @Call()
EndProcedure

Procedure ExamineCalls(*pntr)
  ForEach Call()
    
    If @Call() = *pntr    ; to avoid decreasing the same delay_frames field more than once in a single frame
      Call()\delay_frames - 1
      ; if frame counter is zero
      If Not Call()\delay_frames
        *ret_val = @Call()      ; remember the address before destroying the task
        DeleteElement (Call())  ; the call has to be made so we can now destroy the element
        ProcedureReturn *ret_val
      EndIf
    EndIf
      
  Next Call()
EndProcedure

; Example

Define frame.i = 1

Define *explosion = CreateCall(3)     ;one call after 3 frames
Define *new_enemies = CreateCall(7)   ;one call after 7 frames

; both calls at the same frame
; Define *explosion = CreateCall(7)     ;one call after 7 frames
; Define *new_enemies = CreateCall(7)   ;one call after 7 frames

Repeat
  
  Debug frame
  
  ; I had to replace Select-Case with Ifs because
  ; more calls can 'fall' into the same frame
  If ExamineCalls(*explosion) = *explosion
    Debug "Call explosion procedure"
  EndIf
  
  If ExamineCalls(*new_enemies) = *new_enemies
    Debug "Call new_enemies procedure"
  EndIf
  
  Delay(1000) ; Frame
  frame + 1
  
ForEver
The difference is there's no need to mind the real time and there won't be need to schedule more than one call at once (since I'd like to be able to everytime call the procedure with different parameters).

So, furthermore (on above), what I'd be like to be able to do is to:

- put the procedure calls in queue both with the parameters
- put the same procedure in a queue more than once even before the occurrence of the first call


The meaning of the code from my first post would be:

Code: Select all

For n = 1 To 20
  RegisterTask(@create_explosion(), 10*n, #False)
  ; explosion after 10 frames, after 20 frames, after 30 frames, after 40 frames, ...
  ; each explosion type can have its own frame number and max number of the frames
Next
;new stage after 500 frames
RegisterTask(@create_new_enemies(), 500, #False)  ;5 s after the last explosion
; on 60 fps the last explosion would start after 20*10 = 200 frames, so 500th
; frame is 5 seconds (5*60 = 300) after the beginning of the last explosion
[/size]
User avatar
STARGÅTE
Addict
Addict
Posts: 2277
Joined: Thu Jan 10, 2008 1:30 pm
Location: Germany, Glienicke
Contact:

Re: Delayed procedure calls (frame based)?

Post by STARGÅTE »

oke, you will not a realtime base, but a frame base.
And you will "define" your procedures before.

Here your code:

Code: Select all

Prototype.i TaskFunction(Parameter.i)

Structure Task
	Function.TaskFunction
	DelayFrames.i
	Parameter.i
EndStructure

Global NewList Task.Task()

Procedure RegisterTask(Function.TaskFunction, DelayFrames, Parameter.i=#False)
	AddElement(Task())
	Task()\Function    = Function
	Task()\DelayFrames = DelayFrames
	Task()\Parameter   = Parameter
	ProcedureReturn @Task()
EndProcedure

Procedure ExamineTasks()
	ForEach Task()
		If Task()\DelayFrames
			Task()\DelayFrames - 1 ; wait...
		Else
			Task()\Function(Task()\Parameter) ; execute task
			DeleteElement(Task())
		EndIf
	Next
EndProcedure

; example

Procedure create_explosion(Parameter.i)
	Debug "Call create_explosion procedure"
EndProcedure

Procedure create_new_enemies(Parameter.i)
	Debug "Call create_new_enemies procedure, parameter: "+Str(Parameter)
EndProcedure

For n = 1 To 3
  RegisterTask(@create_explosion(), 50*n, #False)
Next
RegisterTask(@create_new_enemies(), 300, 12345)


Repeat
	
	ExamineTasks()
	
	Delay(10)
	
ForEver
if you need more parameters, define it in Prototype and RegisterTask and in the "; execute task"-Line
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Lizard - Script language for symbolic calculations and moreTypeface - Sprite-based font include/module
User avatar
spacesurfer
User
User
Posts: 15
Joined: Fri May 11, 2012 8:22 am
Location: Hrvatska

Re: Delayed procedure calls (frame based)?

Post by spacesurfer »

STARGÅTE wrote:oke, you will not a realtime base, but a frame base.
And you will "define" your procedures before.

Here your code:

if you need more parameters, define it in Prototype and RegisterTask and in the "; execute task"-Line
That's it!! Thank you :-) that is exactly what I ment!!

Let us yet see if I've understood everything clearly... If we have more procedures with different number of parameters, we have to declare all of them in the prototype and to pass #false for the parameters which the procedure doesn't use? For example, if the procedures are declared like this:

Code: Select all

Procedure create_explosion(type.b, x.w, y.w, vx.b, vy.b, anim_speed.b)  ; 6 parameters
  ;something
EndProcedure

Procedure create_new_enemies(enemy_ID.b, number.b, formation_type.b, AI_level.b)  ; 4 parameters
  ;something
EndProcedure
we would declare a prototype similar to:

Code: Select all

Prototype.i TaskFunction(param_1.w, param_2.w, param_3.w, param_4.w, param_5.w, param_6.w)  ; 6 parameters
so it could handle both of the procedures (word is to handle both word and byte)?

What if one or more of the procedures would need one or more float parameters? Would we then be forced to declare all parameters as float and use type casting?

It's not really necessary, but is there a way to handle the more complicated situation, for example if we would have these procedures:

Code: Select all

Procedure create_explosion(type.b, x.w, y.w, vx.b, vy.b, anim_speed.b)  ; 6 parameters
  ;something
EndProcedure

Procedure create_new_enemies(enemy_ID.b, number.b, formation_type.b, AI_level.b)  ; 4 parameters
  ;something
EndProcedure

Procedure display_data(Data.d, command_ID.b)  ; double
  ;something
EndProcedure

Procedure delayed_message(output_screen_ID.b, message.s)  ; string
  ;something
EndProcedure
User avatar
STARGÅTE
Addict
Addict
Posts: 2277
Joined: Thu Jan 10, 2008 1:30 pm
Location: Germany, Glienicke
Contact:

Re: Delayed procedure calls (frame based)?

Post by STARGÅTE »

the different parameters represent a problem.

If the call differend to the prototy definition, you get a stack defect ... IMA

The simplest way is, you use only one Parameter (like Threads) give only a Pointer (to a structure) to the procedure:

Code: Select all

Structure explosion
  type.b
  x.w
  ;...
EndStructure

Procedure create_explosion(*Parameters.explosion)
;...
EndProcedure

;...
Define MyParameters.explosion
MyParameters\type = 1
MyParameters\x = 2345
RegisterTask(@create_explosion(), 300, @MyParameters)
problem (info!), the Buffer (MyParameters) musst be allocate till the procedure is called.

PB not allowed various parameter types at same position, so you need for all procedure an owe "RegisterTask"-procedure with a prototype.
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Lizard - Script language for symbolic calculations and moreTypeface - Sprite-based font include/module
User avatar
spacesurfer
User
User
Posts: 15
Joined: Fri May 11, 2012 8:22 am
Location: Hrvatska

Re: Delayed procedure calls (frame based)?

Post by spacesurfer »

STARGÅTE wrote:the different parameters represent a problem.

If the call differend to the prototy definition, you get a stack defect ... IMA

The simplest way is, you use only one Parameter (like Threads) give only a Pointer (to a structure) to the procedure:

problem (info!), the Buffer (MyParameters) musst be allocate till the procedure is called.
So I could make another linked list with the parameter structures.
STARGÅTE wrote:PB not allowed various parameter types at same position, so you need for all procedure an owe "RegisterTask"-procedure with a prototype.
I think I'll be able to pass the data to a few different procedures thru just one prototype by passing the redundant data to the procedures that need less parameters and simply ignore what is unnecessary and by passing the byte data thru the word variables.

Thank you again, you've helped me a lot!

Regards
Post Reply