-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Clean godot-xr-tools to have no warnings against gdlint (https://gith…
…ub.com/Scony/godot-gdscript-toolkit) version 3.4.0 (#310) Added github workflow to run gdlint. Fix runs-on declaration for action Added python-version specification to remove action warning.
- Loading branch information
1 parent
b95c610
commit b2d0d30
Showing
28 changed files
with
697 additions
and
426 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Workflow to automatically lint gdscript code | ||
name: gdlint on push | ||
|
||
on: | ||
[push, pull_request] | ||
|
||
jobs: | ||
gdlint: | ||
name: gdlint scripts | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v3 | ||
- name: Set up Python | ||
uses: actions/setup-python@v4 | ||
with: | ||
python-version: '3.10' | ||
- name: Install Dependencies | ||
run: | | ||
python -m pip install --upgrade pip | ||
python -m pip install 'gdtoolkit==3.*' | ||
- name: Lint Godot XR Tools | ||
run: | | ||
gdlint addons/godot-xr-tools |
301 changes: 178 additions & 123 deletions
301
addons/godot-xr-tools/assets/resource_queue/resource_queue.gd
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,166 +1,221 @@ | ||
class_name XRToolsResourceQueue | ||
extends Node | ||
|
||
var thread | ||
var mutex | ||
var semaphore | ||
var exit_thread = false | ||
|
||
var time_max = 100 # Milliseconds. | ||
## XRTools Threaded Resource Loader | ||
## | ||
## This class can be used to perform resource loading in the background without | ||
## interrupting the application. | ||
|
||
var queue = [] | ||
var pending = {} | ||
|
||
func _lock(_caller): | ||
mutex.lock() | ||
# Thread to perform the loading | ||
var _thread : Thread | ||
|
||
# Mutex for safe synchronization | ||
var _mutex : Mutex | ||
|
||
func _unlock(_caller): | ||
mutex.unlock() | ||
# Semaphore to wake up the loader thread | ||
var _semaphore : Semaphore | ||
|
||
# Thread exit flag | ||
var _exit_thread : bool = false | ||
|
||
func _post(_caller): | ||
semaphore.post() | ||
# Queue of ResourceInteractiveLoader instances loading resources | ||
var _queue = [] | ||
|
||
# Dictionary of pending results by resource path. | ||
# | ||
# If the resource is still loading then the value is a ResourceInteractiveLoader. | ||
# If the resource has loaded then the value is a Resource. | ||
var _pending = {} | ||
|
||
func _wait(_caller): | ||
semaphore.wait() | ||
|
||
## Queue the loading of a resource | ||
func queue_resource(path : String, p_in_front : bool = false) -> void: | ||
# Lock the synchronization mutex | ||
_mutex.lock() | ||
|
||
func queue_resource(path, p_in_front = false): | ||
_lock("queue_resource") | ||
if path in pending: | ||
_unlock("queue_resource") | ||
# Test if the resource has already been queued | ||
if path in _pending: | ||
# Work already queued, nothing to do. | ||
_mutex.unlock() | ||
return | ||
elif ResourceLoader.has_cached(path): | ||
var res = ResourceLoader.load(path) | ||
pending[path] = res | ||
_unlock("queue_resource") | ||
|
||
# Test if the ResourceLoader already has it cached | ||
if ResourceLoader.has_cached(path): | ||
# Put the resource in the pending-results dictionary | ||
_pending[path] = ResourceLoader.load(path) | ||
_mutex.unlock() | ||
return | ||
|
||
# Construct a ResourceInteractiveLoader for the resource | ||
var loader = ResourceLoader.load_interactive(path) | ||
|
||
# Save the resource path in the metadata for later use | ||
loader.set_meta("path", path) | ||
|
||
# Insert into the loading-queue (front for high priority) | ||
if p_in_front: | ||
_queue.push_front(loader) | ||
else: | ||
var res = ResourceLoader.load_interactive(path) | ||
res.set_meta("path", path) | ||
if p_in_front: | ||
queue.insert(0, res) | ||
else: | ||
queue.push_back(res) | ||
pending[path] = res | ||
_post("queue_resource") | ||
_unlock("queue_resource") | ||
return | ||
_queue.push_back(loader) | ||
|
||
# Save the loader in the pending-results dictionary | ||
_pending[path] = loader | ||
|
||
func cancel_resource(path): | ||
_lock("cancel_resource") | ||
if path in pending: | ||
if pending[path] is ResourceInteractiveLoader: | ||
queue.erase(pending[path]) | ||
pending.erase(path) | ||
_unlock("cancel_resource") | ||
# Post the semaphore to wake the worker thread | ||
_mutex.unlock() | ||
_semaphore.post() | ||
|
||
|
||
func get_progress(path): | ||
_lock("get_progress") | ||
var ret = -1 | ||
if path in pending: | ||
if pending[path] is ResourceInteractiveLoader: | ||
ret = float(pending[path].get_stage()) / float(pending[path].get_stage_count()) | ||
else: | ||
ret = 1.0 | ||
_unlock("get_progress") | ||
return ret | ||
## Cancel loading a resource | ||
func cancel_resource(path : String) -> void: | ||
# Lock the synchronization mutex | ||
_mutex.lock() | ||
|
||
# Inspect the pending-results dictionary | ||
if path in _pending: | ||
# Extract the item from the pending-results dictionary | ||
var item = _pending[path] | ||
_pending.erase(path) | ||
|
||
func is_ready(path): | ||
var ret | ||
_lock("is_ready") | ||
if path in pending: | ||
ret = !(pending[path] is ResourceInteractiveLoader) | ||
else: | ||
ret = false | ||
_unlock("is_ready") | ||
return ret | ||
# If the item is still being loaded then remove it from the loading-queue | ||
if item is ResourceInteractiveLoader: | ||
_queue.erase(item) | ||
|
||
# Loading cancelled | ||
_mutex.unlock() | ||
|
||
func _wait_for_resource(res, path): | ||
_unlock("wait_for_resource") | ||
while true: | ||
VisualServer.sync() | ||
OS.delay_usec(16000) # Wait approximately 1 frame. | ||
_lock("wait_for_resource") | ||
if queue.size() == 0 || queue[0] != res: | ||
return pending[path] | ||
_unlock("wait_for_resource") | ||
|
||
|
||
func get_resource(path): | ||
_lock("get_resource") | ||
if path in pending: | ||
if pending[path] is ResourceInteractiveLoader: | ||
var res = pending[path] | ||
if res != queue[0]: | ||
var pos = queue.find(res) | ||
queue.remove(pos) | ||
queue.insert(0, res) | ||
|
||
res = _wait_for_resource(res, path) | ||
pending.erase(path) | ||
_unlock("return") | ||
return res | ||
|
||
## Get the progress of a loading resource | ||
func get_progress(path : String) -> float: | ||
# Lock the synchronization mutex | ||
_mutex.lock() | ||
|
||
# Inspect the pending results dictionary for the progress | ||
var progress := -1.0 | ||
if path in _pending: | ||
var item = _pending[path] | ||
if item is ResourceInteractiveLoader: | ||
# The item is still loading, calculate the progress | ||
progress = float(item.get_stage()) / float(item.get_stage_count()) | ||
else: | ||
var res = pending[path] | ||
pending.erase(path) | ||
_unlock("return") | ||
return res | ||
else: | ||
_unlock("return") | ||
return ResourceLoader.load(path) | ||
# The item is fully loaded | ||
progress = 1.0 | ||
|
||
# Return the progress | ||
_mutex.unlock() | ||
return progress | ||
|
||
|
||
func thread_process(): | ||
_wait("thread_process") | ||
_lock("process") | ||
## Test if a resouece is ready | ||
func is_ready(path : String) -> bool: | ||
# Lock the synchronization mutex | ||
_mutex.lock() | ||
|
||
while queue.size() > 0: | ||
var res = queue[0] | ||
_unlock("process_poll") | ||
var ret = res.poll() | ||
_lock("process_check_queue") | ||
# Inspect the pending results dictionary for the ready status | ||
var ready := false | ||
if path in _pending: | ||
var item = _pending[path] | ||
ready = not item is ResourceInteractiveLoader | ||
|
||
if ret == ERR_FILE_EOF || ret != OK: | ||
var path = res.get_meta("path") | ||
if path in pending: # Else, it was already retrieved. | ||
pending[res.get_meta("path")] = res.get_resource() | ||
# Something might have been put at the front of the queue while | ||
# we polled, so use erase instead of remove. | ||
queue.erase(res) | ||
_unlock("process") | ||
# Return the ready status | ||
_mutex.unlock() | ||
return ready | ||
|
||
|
||
func thread_func(_u): | ||
## Get the resource | ||
func get_resource(path : String) -> Resource: | ||
# Lock the synchronization mutex | ||
_mutex.lock() | ||
|
||
# Test if the resource is unknown | ||
if not path in _pending: | ||
# Not queued, just load immediately | ||
_mutex.unlock() | ||
return ResourceLoader.load(path) | ||
|
||
# Loop waiting for resource to load | ||
var res | ||
while true: | ||
mutex.lock() | ||
var should_exit = exit_thread # Protect with Mutex. | ||
mutex.unlock() | ||
# Get the item from the pending-results dictionary | ||
res = _pending[path] | ||
|
||
if should_exit: | ||
# Detect loading complete | ||
if res == null or res is Resource: | ||
break | ||
thread_process() | ||
|
||
# Give thread more time to load the item | ||
_mutex.unlock() | ||
OS.delay_usec(16000) # Wait approximately 1 frame. | ||
_mutex.lock(); | ||
|
||
_pending.erase(path) | ||
_mutex.unlock() | ||
return res | ||
|
||
|
||
## Start the resource queue | ||
func start(): | ||
mutex = Mutex.new() | ||
semaphore = Semaphore.new() | ||
thread = Thread.new() | ||
thread.start(self, "thread_func", 0) | ||
_mutex = Mutex.new() | ||
_semaphore = Semaphore.new() | ||
_thread = Thread.new() | ||
_thread.start(self, "_thread_func", 0) | ||
|
||
|
||
# Triggered by calling "get_tree().quit()". | ||
func _exit_tree(): | ||
mutex.lock() | ||
exit_thread = true # Protect with Mutex. | ||
mutex.unlock() | ||
_mutex.lock() | ||
_exit_thread = true # Protect with Mutex. | ||
_mutex.unlock() | ||
|
||
# Unblock by posting. | ||
semaphore.post() | ||
# Wake the worker thread | ||
_semaphore.post() | ||
|
||
# Wait until it exits. | ||
thread.wait_to_finish() | ||
_thread.wait_to_finish() | ||
|
||
|
||
# Thread worker function | ||
func _thread_func(_u): | ||
# Lock the synchronization mutex | ||
_mutex.lock() | ||
|
||
# Loop processing work | ||
while true: | ||
# Handle a request to terminate | ||
if _exit_thread: | ||
_mutex.unlock() | ||
return | ||
|
||
# Handle no work | ||
if _queue.size() == 0: | ||
# Wait for work (with the mutex unlocked so work can be added) | ||
_mutex.unlock() | ||
_semaphore.wait() | ||
_mutex.lock() | ||
continue | ||
|
||
# Get the loader | ||
var loader : ResourceInteractiveLoader = _queue.front() | ||
|
||
# Poll the loader (with the mutex unlocked) | ||
_mutex.unlock() | ||
var err = loader.poll() | ||
_mutex.lock() | ||
|
||
# If loader is still busy then continue | ||
if err == OK: | ||
continue | ||
|
||
# Remove from the loading-queue. Note that something may have been | ||
# put at the front of the queue while we polled, so use erase instead | ||
# of remove | ||
_queue.erase(loader) | ||
|
||
# Get the resource path from the loaders metadata | ||
var path : String = loader.get_meta("path") | ||
|
||
# If the result is still pending then update it with the resource | ||
if path in _pending: | ||
_pending[path] = loader.get_resource() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.