Is there any way to perform raycasting in a separate thread? Simple experimentation with moving raycast3D to a subthread shows that space state can only be accessed from the main thread (I presume because it's associated with the active scene tree). However I would be happy to know if there is a way to run them on a separate thread in case I need to process many raycasts without overloading the main thread.
Raycast possible in separate thread?
In addition, would using a raycast3D node and calling force_raycast_update
faster than constructing multiple queries for PhysicsDirectSpaceState3D
?
xyz Nope, even calls using the state object sourced from outside of physics_process() results in "space->locked" errors.
test code:
var thread = Thread.new()
func _ready():
var dss = await AW_get_dss()
var c = Callable(self,"thread_func")
thread.start(thread_func.bind(dss))
print("done!")
func thread_func(dss):
randomize()
for x in range(90000):
var p1 = Vector3(randf_range(0,20),randf_range(0,20),randf_range(0,20))
var p2 = Vector3(randf_range(0,20),randf_range(0,20),randf_range(0,20))
var r = simplified_line_of_sight_test([p1,p2,dss])
print(str(x)+": "+str(r))
func _physics_process(delta):
var p1 = Vector3(randf_range(0,20),randf_range(0,20),randf_range(0,20))
var p2 = Vector3(randf_range(0,20),randf_range(0,20),randf_range(0,20))
var dss = get_world_3d().direct_space_state
var r = simplified_line_of_sight_test([p1,p2,dss])
print(str("phy")+": "+str(r))
func AW_get_dss():
await get_tree().physics_frame
return get_world_3d().direct_space_state
func simplified_line_of_sight_test(params_arr):
var perception_pos = params_arr[0]
var perceived_entity_pos = params_arr[1]
var dss = params_arr[2]
var exclude_arr = []
#do raycasts
var is_obstructed = true
var x = PhysicsRayQueryParameters3D.new()
x.from = perception_pos
x.to = perceived_entity_pos
x.exclude = exclude_arr
var _result = dss.intersect_ray( x )
if _result.size() == 0:
is_obstructed = false
return !is_obstructed
There seems to be no way of offloading physics from the physics thread. This is now an issue for me because it means that every time I want to do a raycast from any other thread, I need to await a physics process frame.
I could try and force the physics thread into an alternative process loop that only updates upon request (like I did for threads here), allowing me to make physics calls without delay, but doing that would disable all physics_process() loops across my entire project, and if any nodes rely on physics_process() they would likely cease to work.
Overall really sucks; what if you want to make a bunch of disjointed physics calls without forcing the physics process to stall or being forced to wait upon process frames several times? Ideally there would be a way to duplicate the direct_space_state object to make physics tests independently from the physics server.
KnightNine Yep. You're right. Doing raycasts require interaction with the scene tree. And scene tree is in general not thread safe. See if you can use alternative approaches to your things that don't require casting.
I recently had a similar thing. I pushed some heavily raycasting code onto a thread and ended up generating and sampling a heightmap image instead of doing raycasts to find the intersection points. Of course, alternatives are not always possible. In that case, try to distribute casts through multiple frames.
xyz I made a feature request, but in the process of trying to work around this issue, I found out that I can't even halt the physics_process() loop without also halting the main thread, turns out physics threading doesn't work how I expected it to so this system doesn't really work without slowing the main thread:
var physics_thread_held = false
var physics_thread_semaphore = Semaphore.new()
var physics_thread_semaphore_c = 0
var caller_semaphore = Semaphore.new()
var caller_semaphore_c = 0
#number of function calls in the held physics thread before allowing a physics frame to pass in order to smooth out the loading sequence and lower the ammount of stuttering
const HELD_PHYSICS_THREAD_FRAME_PASS_ITERATION = 50
var iteration = 0
var physics_function_object
var physics_function_name
var physics_function_params
var physics_function_result
func held_physics_thread_func():
while physics_thread_held:
physics_thread_semaphore.wait()
# if physics_thread_semaphore_c > 1:
# OS.alert("whoops physics_thread_semaphore_c")
# System.system_breakpoint()
physics_thread_semaphore_c-=1
iteration+=1
#only allow the call to be made when the semaphore post count is 0 so that this never begins to overlap with other calls to this loop
if physics_function_name != null:
Logger.info(physics_function_params)
physics_function_result = physics_function_object.call(physics_function_name,physics_function_params)
#after physics_function_result retreived, allow caller function to return
caller_semaphore_c+=1
Logger.info("caller_semaphore_c:"+str(caller_semaphore_c))
physics_function_object = null
physics_function_name = null
caller_semaphore.post()
if iteration > HELD_PHYSICS_THREAD_FRAME_PASS_ITERATION:
Logger.info("held_physics_frame_pass!")
iteration = 0
await get_tree().physics_frame
Logger.info("physics thread released!")
#call this first
func hold_physics_thread():
if !physics_thread_held:
physics_thread_held = true
await get_tree().physics_frame
held_physics_thread_func()
func release_physics_thread():
if physics_thread_held:
physics_thread_held = false
physics_thread_semaphore.post()
func call_from_held_physics_thread(func_object,func_name,func_params):
if !physics_thread_held:
Logger.error("Invalid call_from_held_physics_thread call: physics thread not held.")
return
physics_function_object = func_object
physics_function_name = func_name
physics_function_params = func_params
physics_thread_semaphore.post()
#wait till function is complete in physics thread
caller_semaphore.wait()
if physics_function_result == null:
#this shouldn't happen
System.system_breakpoint()
return physics_function_result
Is there any way to check if the physics thread is locked? I think I'll need to modify the engine so that the physics functions return null instead of false when the space is locked so that I can simply spam raycasts in a while loop until I get a proper result when calling it from a thread. (that is if I can't create a
get_is_space_locked()
function )
kuligs2 I explored Mutex but somehow I can still manage to get a few space locked errors in my test code (first comment) when wrapping the simplified_line_of_sight_test()
in a mutex.
Do you have any idea why this is the case?
KnightNine its out of my expertise scope, im not smarts enough.. the best advice you can get is from xyz.
KnightNine I'd just do it from the main thread, little by little. What actual problem are you trying to solve with raycasts here?
- Edited
xyz I need to get my AI algorithm to know the sightlines at various positions that AI controlled characters can move to, and make calculations to essentially answer the question: "at this position: am I able to target who I want to target and am I exposed to be targeted by hostiles that are not my target?".
This is a lot of scans because there are a lot of positions to generate viability scores for. I don't want to slow or halt the framerate to make these calculations. Depending on the AI's character's capacity for movement and its effective range, these calculations can take 1-5 seconds if a viable position isn't found in the first few scans, so doing it "little by little" from the main thread can exponentially slow down this scanning process. The player is waiting for the AI throughout this scanning process due to being similar to a turn-based game, passive processing isn't viable.
- Edited
KnightNine Are you sure you need as many raycasts as you're using? I mean how many of them we're talking about? It there a room for optimizations? Can you pre-process at least some of the rayhit queries?
Threads can save you some time but it will still take time so I'd first implement it in the main thread distributed through frames, see how it behaves in practice and look for opportunities to optimize.
Afaik doing queries directly from a thread in a reliable manner is currently a no-go.
- Edited
xyz It's been a long while since I tested my AI code but I think it's around a 500-1500ish (probably less idk). I cannot pre-process the queries because the position of characters are only static when it's the Player or AI's turn. Storing any raycast result data for later use would be negligible in terms of reducing the number of raycasts.
The optimizations I've made involved reducing scan density but I can't do much else without dumbing down the AI.
I could avoid raycasts by checking if every coordinate between the target and the observer is unoccupied but I'm using raycasts in the first place because fetching all that grid-data from a 3d grid is really slow. (but maybe if I use the Astar module to fetch whether or not a line of positions are occupied with a custom "grid_cast()" function, that may be nearly as fast as a raycast.)
The AI being able to process sightlines is an initial step of processing the validity of a position because even if two positions are directly next to each other, one may be behind cover while the other isn't.
So far I'm leaning towards editing the engine code to create a jank solution to this.
Note: Mutex likely doesn't work because the engine is already making calls to the direct_space_state without any user-defined physics functions (this would explain why commenting out the physics call within physics_process() still results in space locked errors.).
Note2: Engine.is_in_physics_frame
tells me when the physics frame is active. in theory there would be a way to make physics calls from threads safely between physics frames, the question is how this could be accomplished; if I could get a mutex that locks before the physics frame, and unlocks after the end of said frame, then I could safely call physics functions from a thread.
Either way I'll figure something out.
- Edited
KnightNine What exactly are you checking with those raycasts? Sounds like you're doing a lot of bruteforce.
If this is a grid based system, you really may not need raycasts at all.