I have a game where I have multiple character that pathfind around an area. I have a pathfind function which can take around a minute to complete. I want to be able to have multiple pathfind functions running at the same time, but not start them at the same time. I tried using sync which allowed me to run them simultaneously, but I couldn’t have any delay between starting the functions.
What concurrency type should I be using where I can call the function at any time and it will still allow me to run it, even if it is already running with a different character?
You can still use a sync and have a delay at the start of each pathfind function or just prior to the call of each pathfind function.
sync:
block: # groups a bunch of expressions as a single expression
# Could be your own delaying function with `event` class etc.
Sleep(GetRandomSeconds())
Guy1.Pathfind()
block:
Sleep(GetRandomSeconds())
Guy2.Pathfind()
block:
Sleep(GetRandomSeconds())
Guy3.Pathfind()
Or you can use race which is similar to sync except that it stops the other sub-expressions as soon as one of the sub-expressions completes:
race: # Whichever Guy finishes first wins and the other PathFind() calls are cancelled
block:
Sleep(GetRandomSeconds())
Guy1.Pathfind()
block:
Sleep(GetRandomSeconds())
Guy2.Pathfind()
block:
Sleep(GetRandomSeconds())
Guy3.Pathfind()
There are many ways to do what you are asking.
Hopefully this helps. You could give additional info if you want more ideas.
Waits specified number of seconds and then resumes.
If Seconds = 0.0 then it waits until next tick/frame/update. Waiting until the next update (0.0) is especially useful in a loop of a coroutine that needs to do some work every update and this yields to other coroutines so that it doesn’t hog a processor’s resources.
If Seconds = Inf then it waits forever and can be cancelled - such as via race. Waiting forever (Inf) will have any expression that follows never be evaluated. Occasionally it is desirable to have a task never complete such as the last expression in a race subtask where the task must never win the race though it still may be cancelled earlier.
If Seconds < 0.0 then it completes immediately and does not yield to other aysnc expressions. Immediately completing (less than 0) is useful when you want programmatic control over whether an expression yields or not.
event is in the Verse module. Here is its definition:
#----------------------------------------------------------------------------------------
# A *recurring* successively signaled event allowing a simple mechanism to coordinate
# between concurrent tasks:
# - several tasks wait on the event
# - another task signals the event and resumes any waiting tasks
event<native><public>(t:type) := class(signalable(t), awaitable(t)):
# Returns number of calls to `Await()` that currently have their tasks suspended
# and are waiting for the signaling of this event.
GetAwaitCount<native><epic_internal>():int
# Suspends/blocks the current task until this event is signaled by having another
# task call `Signal()`. If called in the middle of a `Signal()` then it is still
# suspended and resumed during the next call to `Signal()`.
Await<native><override>()<suspends>:t
# Concurrently (simultaneously within the call) resumes the tasks of any Await()
# calls that were suspended until the signaling of this event.
#
# The Await() calls' tasks are resumed in the order that they were suspended.
# Each task will do as much atomic work as it can until it encounters a block/
# yield in the form of a call to a coroutine/async call whereupon it will
# cooperatively transfer control to the next Await() task until all the
# tasks are resumed.
#
# If this is called while already in the middle of a `Signal()` call, it asserts.
#
# The resumed Await() tasks can have more calls to `Await()` in the middle of
# this call to `Signal()` before they yield. Any such new Await() calls will suspend
# and queue up for the next call to `Signal()`
Signal<native><override>(:t):void