add addons and swarm engine skeleton

This commit is contained in:
2025-02-17 19:52:35 +01:00
parent 002e6ae41c
commit 80b6b7daab
110 changed files with 8665 additions and 1 deletions

View File

@@ -0,0 +1,157 @@
// ReSharper disable once CheckNamespace
namespace GodotResourceGroups
{
using Godot;
using System.Collections.Generic;
using System;
public class ResourceGroup
{
private readonly Resource _wrapped;
private ResourceGroup(Resource wrapped)
{
_wrapped = wrapped;
}
/// <summary>
/// Loads a resource group from the given path.
/// </summary>
public static ResourceGroup Of(string path)
{
var wrapped = GD.Load<Resource>(path);
return Of(wrapped);
}
/// <summary>
/// Creates a typesafe wrapper for the given resource group.
/// </summary>
// ReSharper disable once MemberCanBePrivate.Global
public static ResourceGroup Of(Resource wrapped)
{
if (wrapped == null)
{
throw new ArgumentNullException(nameof(wrapped));
}
if (wrapped.GetScript().As<Script>() is not GDScript gdScript ||
!gdScript.ResourcePath.EndsWith("resource_group.gd"))
{
throw new ArgumentException("Resource is not a resource group");
}
return new ResourceGroup(wrapped);
}
/// <summary>
/// Returns all include patterns in this resource group.
/// </summary>
public List<string> Includes => new(_wrapped.Get("includes").AsStringArray());
/// <summary>
/// Returns all exclude patterns in this resource group.
/// </summary>
public List<string> Excludes => new(_wrapped.Get("excludes").AsStringArray());
/// <summary>
/// Returns all paths in this resource group.
/// </summary>
public List<string> Paths => new(_wrapped.Get("paths").AsStringArray());
/// <summary>
/// Loads all resources in this resource group.
/// </summary>
/// <returns></returns>
public List<Resource> LoadAll() => new(_wrapped.Call("load_all").AsGodotObjectArray<Resource>());
/// <summary>
/// Loads all resources in this resource group in the background. The given callback will be called
/// for each resource that is loaded. The callback will be called from the main thread.
/// </summary>
/// <returns>A <see cref="ResourceGroupBackgroundLoader"/> which can be used to cancel the background loading.</returns>
public ResourceGroupBackgroundLoader LoadAllInBackground(
Action<ResourceGroupBackgroundLoader.ResourceLoadingInfo> callback)
{
var godotCallback =
Callable.From<GodotObject>(it => callback(new ResourceGroupBackgroundLoader.ResourceLoadingInfo(it)));
var wrapped = _wrapped.Call("load_all_in_background", godotCallback);
return new ResourceGroupBackgroundLoader(wrapped.AsGodotObject());
}
/// <summary>
/// Loads all resources that match the given include and exclude specification from this resource group in the background.
/// The given callback will be called for each resource that is loaded. The callback will be called from the main thread.
/// </summary>
/// <returns>A <see cref="ResourceGroupBackgroundLoader"/> which can be used to cancel the background loading.</returns>
public ResourceGroupBackgroundLoader LoadMatchingInBackground(IEnumerable<string> includes,
IEnumerable<string> excludes,
Action<ResourceGroupBackgroundLoader.ResourceLoadingInfo> callback)
{
var godotCallback =
Callable.From<GodotObject>(it => callback(new ResourceGroupBackgroundLoader.ResourceLoadingInfo(it)));
var wrapped = _wrapped.Call("__csharp_load_matching_in_background", ToArray(includes), ToArray(excludes),
godotCallback);
return new ResourceGroupBackgroundLoader(wrapped.AsGodotObject());
}
/// <summary>
/// Loads all items of the group into the given collection. If an item is of the wrong
/// type it will be skipped and an error is printed.
/// </summary>
public void LoadAllInto<T>(ICollection<T> destination)
{
var items = _wrapped.Call("load_all").AsGodotObjectArray<Resource>();
PushInto(items, destination);
}
/// <summary>
/// Returns all paths in this resource group that match the given patterns.
/// </summary>
public List<string> GetMatchingPaths(IEnumerable<string> includes, IEnumerable<string> excludes)
=> new(_wrapped.Call("__csharp_get_matching_paths", ToArray(includes), ToArray(excludes)).AsStringArray());
/// <summary>
/// Returns all resources in this resource group that match the given patterns.
/// </summary>
public List<Resource> LoadMatching(IEnumerable<string> includes, IEnumerable<string> excludes)
=> new(_wrapped.Call("__csharp_load_matching", ToArray(includes), ToArray(excludes))
.AsGodotObjectArray<Resource>());
/// <summary>
/// Loads all resources in this resource group that match the given patterns and stores them
/// into the given collection. If an item is of the wrong type it will be skipped and an
/// error is printed.
/// </summary>
public void LoadMatchingInto<T>(ICollection<T> destination, IEnumerable<string> includes,
IEnumerable<string> excludes)
{
var items = _wrapped.Call("__csharp_load_matching", ToArray(includes), ToArray(excludes))
.AsGodotObjectArray<Resource>();
PushInto(items, destination);
}
private static string[] ToArray(IEnumerable<string> enumerable)
{
return enumerable as string[] ?? new List<string>(enumerable).ToArray();
}
private static void PushInto<T>(IEnumerable<Resource> items, ICollection<T> destination)
{
foreach (var item in items)
{
if (item is T t)
{
destination.Add(t);
}
else
{
GD.PushError("Item ", item, " is not of required type ", typeof(T).Namespace, ". Skipping.");
}
}
}
}
}

View File

@@ -0,0 +1,50 @@
using Godot;
// ReSharper disable once CheckNamespace
namespace GodotResourceGroups;
/// <summary>
/// A background loader for resource groups. Use <see cref="ResourceGroup.LoadAllInBackground"/> or
/// <see cref="ResourceGroup.LoadMatchingInBackground"/>to create an instance of this class.
/// </summary>
public class ResourceGroupBackgroundLoader
{
private readonly GodotObject _wrapped;
internal ResourceGroupBackgroundLoader(GodotObject wrapped)
{
_wrapped = wrapped;
}
/// <summary>
/// Cancels the background loading. The callback will not be called anymore after this method returns.
/// </summary>
public void Cancel()
{
_wrapped.Call("cancel");
}
/// <summary>
/// Returns true if the background loading is done. Will also return true if the loading was cancelled.
/// </summary>
public bool IsDone()
{
return _wrapped.Call("is_done").As<bool>();
}
public readonly struct ResourceLoadingInfo
{
private readonly GodotObject _wrapped;
public ResourceLoadingInfo(GodotObject wrapped)
{
_wrapped = wrapped;
}
public bool Success => _wrapped.Get("success").As<bool>();
public string Path => _wrapped.Get("path").As<string>();
public Resource Resource => _wrapped.Get("resource").As<Resource>();
public float Progress => _wrapped.Get("progress").As<float>();
public bool Last => _wrapped.Get("last").As<bool>();
}
}

View File

@@ -0,0 +1,62 @@
@tool
extends EditorPlugin
const ResourceScanner = preload("resource_scanner.gd")
const ResourceGroupScanner = preload("resource_group_scanner.gd")
const ResourceGroupsExportPlugin = preload("godot_resource_groups_export_plugin.gd")
const REBUILD_SETTING: StringName = "godot_resource_groups/auto_rebuild"
var _group_scanner: ResourceGroupScanner
var _export_plugin: ResourceGroupsExportPlugin
func _enter_tree():
add_tool_menu_item("Rebuild project resource groups", _rebuild_resource_groups)
_group_scanner = ResourceGroupScanner.new(get_editor_interface().get_resource_filesystem())
# try to get the setting, if it doesn't exist, set it to true
var auto_rebuild: bool = ProjectSettings.get_setting(REBUILD_SETTING, true)
# make sure it is there
ProjectSettings.set_setting("godot_resource_groups/auto_rebuild", auto_rebuild)
# add property info so it shows up in the editor
ProjectSettings.add_property_info({
"name": "godot_resource_groups/auto_rebuild",
"description": "Automatically rebuild resource groups when the project is built.",
"type": TYPE_BOOL,
})
# register the export plugin
_export_plugin = ResourceGroupsExportPlugin.new(_rebuild_resource_groups)
add_export_plugin(_export_plugin)
func _exit_tree():
remove_tool_menu_item("Rebuild project resource groups")
remove_export_plugin(_export_plugin)
func _build() -> bool:
# always read the setting to make sure it is up to date
var auto_rebuild: bool = ProjectSettings.get_setting(REBUILD_SETTING, true)
if auto_rebuild:
_rebuild_resource_groups()
return true
func _rebuild_resource_groups():
var start = Time.get_unix_time_from_system()
var groups = _group_scanner.scan()
for group in groups:
var resource_scanner = ResourceScanner.new(group, get_editor_interface().get_resource_filesystem())
var resource_paths = resource_scanner.scan()
group.paths = resource_paths
ResourceSaver.save(group)
get_editor_interface().get_resource_filesystem().update_file(group.resource_path)
var end = Time.get_unix_time_from_system()
print("Rebuilt resource groups in %.2f seconds." % [end-start])

View File

@@ -0,0 +1,14 @@
@tool
extends EditorExportPlugin
var _on_export:Callable
func _init(on_export:Callable):
_on_export = on_export
func _export_begin(features, is_debug, path, flags):
_on_export.call()
func _get_name() -> String:
return "Godot Resource Groups Export Plugin"

View File

@@ -0,0 +1,140 @@
@tool
var _include_regexes:Array[RegEx]
var _exclude_regexes:Array[RegEx]
func _init(base_folder:String, include_patterns:Array[String], exclude_patterns:Array[String]):
# compile the include and exclude patterns to regular expressions, so we don't
# have to do it for each file
_include_regexes = []
_exclude_regexes = []
for pattern in include_patterns:
if pattern == "" or pattern == null:
continue
_include_regexes.append(_compile_pattern(base_folder, pattern))
for pattern in exclude_patterns:
if pattern == "" or pattern == null:
continue
_exclude_regexes.append(_compile_pattern(base_folder, pattern))
## Compiles the given pattern to a regular expression.
func _compile_pattern(base_folder:String, pattern:String) -> RegEx:
# ** - matches zero or more characters (including "/")
# * - matches zero or more characters (excluding "/")
# ? - matches one character
# we convert the pattern to a regular expression
# ** becomes .*
# * becomes [^/]* (any number of characters except /)
# ? becomes [^/] (any character except /)
# all other characters are escaped
# the pattern is anchored at the beginning and end of the string
# the pattern is case-sensitive
var regex = "^" + _escape_string(base_folder) + "/"
var i = 0
var len = pattern.length()
while i < len:
var c = pattern[i]
if c == "*":
if i + 1 < len and pattern[i + 1] == "*":
# ** - matches zero or more characters (including "/")
regex += ".*"
i += 2
else:
# * - matches zero or more characters (excluding "/")
regex += "[^\\/]*"
i += 1
elif c == "?":
# ? - matches one character
regex += "[^\\/]"
i += 1
else:
# escape all other characters
regex += _escape_character(c)
i += 1
regex += "$"
var result = RegEx.new()
result.compile(regex)
return result
func _escape_string(c:String) -> String:
var result = ""
for i in len(c):
result += _escape_character(c[i])
return result
## Escapes the given character for use in a regular expression.
## No clue why this is not built-in.
func _escape_character(c:String) -> String:
if c == "\\":
return "\\\\"
elif c == "^":
return "\\^"
elif c == "$":
return "\\$"
elif c == ".":
return "\\."
elif c == "|":
return "\\|"
elif c == "?":
return "\\?"
elif c == "*":
return "\\*"
elif c == "+":
return "\\+"
elif c == "(":
return "\\("
elif c == ")":
return "\\)"
elif c == "{":
return "\\{"
elif c == "}":
return "\\}"
elif c == "[":
return "\\["
elif c == "]":
return "\\]"
elif c == "/":
return "\\/"
else:
return c
func matches(file:String) -> bool:
# the group definition has a list of include and exclude patterns
# if the list of include patterns is empty, all files match
# any file that matches an exclude pattern is excluded
# we allow * as a wildcard for a single path segment
# we allow ** as a wildcard for multiple path segments
if _include_regexes.size() > 0:
var found = false
# the file must match at least one include pattern
for item in _include_regexes:
if item.search(file) != null:
found = true
break
if not found:
if file.contains(".txt"):
print("file ", file , " did not match any regex")
return false
# the file must not match any exclude pattern
for item in _exclude_regexes:
if item.search(file) != null:
if file.contains(".txt"):
print("file ", file , " was excluded ")
return false
return true

View File

@@ -0,0 +1,7 @@
[plugin]
name="Godot Resource Groups"
description="A plugin which helps you to conveniently access all resources in your project without having to hardcode any paths."
author="Jan Thomä"
version="0.4.1"
script="godot_resource_groups.gd"

View File

@@ -0,0 +1,103 @@
@tool
## A resource group is a set of resources in the project
## that you want to access at runtime.
@icon("resource_group.svg")
class_name ResourceGroup
extends Resource
const PathVerifier = preload("path_verifier.gd")
## The base folder for locating files in this resource group.
@export_dir var base_folder:String = ""
## Files that should be included. Can contain ant-style wildcards:
## ** - matches zero or more characters (including "/")
## * - matches zero or more characters (excluding "/")
## ? - matches one character
@export var includes:Array[String] = []
## Files which should be excluded. Is applied after the include filter.
## Can also contain ant-style wildcards.
@export var excludes:Array[String] = []
## The paths of the project that match this resource group.
@export var paths:Array[String] = []
## Loads all resources in this resource group and returns them.
func load_all() -> Array[Resource]:
var result:Array[Resource] = []
load_all_into(result)
return result
## Loads all resources and stores them into the given array. Allows
## to load resources into typed arrays for better type safety. If
## the item is not of the required type, an error will be printed and
## the item is skipped.
func load_all_into(destination:Array):
for path in paths:
destination.append(load(path))
## Gets all paths of resources inside of this resource group that
## match the given include and exclude criteria
func get_matching_paths(includes:Array[String], excludes:Array[String]) -> Array[String]:
var path_verifier = PathVerifier.new(base_folder, includes, excludes)
return paths.filter(func(it): return path_verifier.matches(it))
## Loads all resources in this resource group that match the given
## include and exclude criteria
func load_matching(includes:Array[String], excludes:Array[String]) -> Array[Resource]:
var result:Array[Resource] = []
load_matching_into(result, includes, excludes)
return result
## Loads all resources in this resource group that match the given
## include and exclude criteria and stores them into the given array.
## Allows to load resources into typed arrays for better type safety. If
## the item is not of the required type, an error will be printed and
## the item is skipped.
func load_matching_into(destination:Array, includes:Array[String], excludes:Array[String]):
var matching_paths = get_matching_paths(includes, excludes)
for path in matching_paths:
destination.append(load(path))
## Loads all resources in this resource group in background. Returns
## a ResourceGroupBackgroundLoader object which can be used to check the
## status and collect the results. Will call the on_resource_loaded callable for each
## loaded resource.
func load_all_in_background(on_resource_loaded:Callable) -> ResourceGroupBackgroundLoader:
return ResourceGroupBackgroundLoader.new(paths, on_resource_loaded)
## Loads all resources in this resource group that match the given
## include and exclude criteria in background. ResourceGroupBackgroundLoader object which can be used to check the
## status and collect the results. Will call the on_resource_loaded callable for each
## loaded resource.
func load_matching_in_background(includes:Array[String], excludes:Array[String], on_resource_loaded:Callable) \
-> ResourceGroupBackgroundLoader:
return ResourceGroupBackgroundLoader.new(get_matching_paths(includes, excludes), on_resource_loaded)
#### CSHARP Specifics ####
# Workaround for C# interop not being able to properly convert arrays into Godot land.
func __csharp_get_matching_paths(includes:Array, excludes:Array) -> Array[String]:
return get_matching_paths(__to_string_array(includes), __to_string_array(excludes))
# Workaround for C# interop not being able to properly convert arrays into Godot land.
func __csharp_load_matching(includes:Array, excludes:Array) -> Array[Resource]:
return load_matching(__to_string_array(includes), __to_string_array(excludes))
# Workaround for C# interop not being able to properly convert arrays into Godot land.
func __csharp_load_matching_in_background(includes:Array, excludes:Array, on_resource_loaded:Callable) -> ResourceGroupBackgroundLoader:
return load_matching_in_background(__to_string_array(includes), __to_string_array(excludes), on_resource_loaded)
# Converts an untyped array to a string array.
func __to_string_array(array:Array) -> Array[String]:
var result:Array[String] = []
for item in array:
result.append(item)
return result

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g id="Cube" transform="matrix(1,0,0,1,-25.7158,-7.96899)">
<path d="M68.796,39.855L86.269,49.736L68.796,59.618L51.323,49.736L68.796,39.855" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
<path d="M51.323,49.736L51.323,69.499L68.796,79.38L68.796,59.618L51.323,49.736" style="fill:rgb(149,136,223);stroke:black;stroke-width:0.15px;"/>
<path d="M86.269,49.736L68.796,59.618L68.796,79.38L86.269,69.499L86.269,49.736Z" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
</g>
<g id="Cube1" serif:id="Cube" transform="matrix(1,0,0,1,16.124,-7.96899)">
<path d="M68.796,39.855L86.269,49.736L68.796,59.618L51.323,49.736L68.796,39.855" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
<path d="M51.323,49.736L51.323,69.499L68.796,79.38L68.796,59.618L51.323,49.736" style="fill:rgb(149,136,223);stroke:black;stroke-width:0.15px;"/>
<path d="M86.269,49.736L68.796,59.618L68.796,79.38L86.269,69.499L86.269,49.736Z" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
</g>
<g id="Cube2" serif:id="Cube" transform="matrix(1,0,0,1,-4.32539,4.5168)">
<path d="M68.796,39.855L86.269,49.736L68.796,59.618L51.323,49.736L68.796,39.855" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
<path d="M51.323,49.736L51.323,69.499L68.796,79.38L68.796,59.618L51.323,49.736" style="fill:rgb(149,136,223);stroke:black;stroke-width:0.15px;"/>
<path d="M86.269,49.736L68.796,59.618L68.796,79.38L86.269,69.499L86.269,49.736Z" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
</g>
<g id="Cube3" serif:id="Cube" transform="matrix(1,0,0,1,-25.7158,16.7339)">
<path d="M68.796,39.855L86.269,49.736L68.796,59.618L51.323,49.736L68.796,39.855" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
<path d="M51.323,49.736L51.323,69.499L68.796,79.38L68.796,59.618L51.323,49.736" style="fill:rgb(149,136,223);stroke:black;stroke-width:0.15px;"/>
<path d="M86.269,49.736L68.796,59.618L68.796,79.38L86.269,69.499L86.269,49.736Z" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
</g>
<g id="Cube4" serif:id="Cube" transform="matrix(1,0,0,1,18.0258,16.7339)">
<path d="M68.796,39.855L86.269,49.736L68.796,59.618L51.323,49.736L68.796,39.855" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
<path d="M51.323,49.736L51.323,69.499L68.796,79.38L68.796,59.618L51.323,49.736" style="fill:rgb(149,136,223);stroke:black;stroke-width:0.15px;"/>
<path d="M86.269,49.736L68.796,59.618L68.796,79.38L86.269,69.499L86.269,49.736Z" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
</g>
<g transform="matrix(1,0,0,1.05859,-2.81137,-0.554868)">
<path d="M118.078,27.093C118.078,19.076 111.188,12.568 102.703,12.568L30.92,12.568C22.435,12.568 15.545,19.076 15.545,27.093L15.545,94.871C15.545,102.887 22.435,109.395 30.92,109.395L102.703,109.395C111.188,109.395 118.078,102.887 118.078,94.871L118.078,27.093Z" style="fill:none;stroke:rgb(193,185,236);stroke-width:2.04px;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bkc024iv5jdeu"
path="res://.godot/imported/resource_group.svg-4ab15486a9423c0a5fbcfc8f79bd7fc4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot_resource_groups/resource_group.svg"
dest_files=["res://.godot/imported/resource_group.svg-4ab15486a9423c0a5fbcfc8f79bd7fc4.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1,126 @@
class_name ResourceGroupBackgroundLoader
var _open:Array[String] = []
var _callback:Callable
var _cancelled:bool = false
var _total:int = 0
var _finished:int = 0
## A loaded resource. This will be given as argument
## to the callback function
class ResourceLoadingInfo:
## Whether loading of this resource was successful
var success:bool
## The path from which the resource was loaded
var path:String
## The loaded resource. Is null when success is false
var resource:Resource
## The overall progress of the background loading, can
## be used to drive a progress indicator
var progress:float
## Whether this has been the last resource
## of the
var last:bool
## Ctor. Takes an array of paths to load.
func _init(paths:Array[String], callback:Callable):
_callback = callback
# copy all into the open array and reverse the order
# so we can use the array as a stack
_open.append_array(paths)
_open.reverse()
# make note of the total amount of things
_total = _open.size()
# start loading
_call_deferred(_next)
## Cancels the currently running background loading process
## as soon as possible. If the process is already finished,
## nothing will happen. This function will immediately return.
func cancel():
_cancelled = true
## Checks if the background loading process is done.
func is_done() -> bool:
return _open.is_empty() or _cancelled
## Looks like call_deferred does not work properly when not in a node
## context (it doesn't seem to defer there...), so we roll our own here.
func _call_deferred(callable:Callable, args:Array = []):
await Engine.get_main_loop().process_frame
callable.callv(args)
## Starts the loading process for the next file in background.
func _next():
# if we're done or the process was cancelled, stop it here.
if _open.is_empty() or _cancelled:
return
# fetch the next path and ask the resource loader to load it.
var path = _open.pop_back()
var result = ResourceLoader.load_threaded_request(path)
if result != OK:
push_warning("Unable to load path ", path , " return code ", result)
_call_deferred(_fetch, [path])
## Tries to fetch a file currently being loaded in background.
func _fetch(path:String):
var status = ResourceLoader.load_threaded_get_status(path)
match (status):
ResourceLoader.THREAD_LOAD_INVALID_RESOURCE:
push_warning("Loading resource at ", path , " failed. Invalid resource. Ignoring and moving on.")
_failed(path)
ResourceLoader.THREAD_LOAD_FAILED:
push_warning("Loading resource at ", path , " failed. Ignoring and moving on.")
_failed(path)
# if its ready, ship it to the callback and move on
ResourceLoader.THREAD_LOAD_LOADED:
var resource = ResourceLoader.load_threaded_get(path)
_succeeded(path, resource)
# we're still loading, so try again next frame
ResourceLoader.THREAD_LOAD_IN_PROGRESS:
_call_deferred(_fetch, [path])
# Called when a path loading ultimately failed. Informs the callback
# and moves on.
func _failed(path:String):
_finished += 1
_call_callback(false, path, null)
_call_deferred(_next)
## Called when a path loading ultimately succeeded. Informs the callback
## and moves on.
func _succeeded(path:String, resource:Resource):
_finished += 1
_call_callback(true, path, resource)
_call_deferred(_next)
## Called when an operation is finished. Will invoke the callback.
func _call_callback(success:bool, path:String, resource:Resource):
# don't call the callback anymore if the process was cancelled
if _cancelled:
return
var progress:float = 1.0 if _total == 0 else float(_finished) / float(_total)
var result = ResourceLoadingInfo.new()
result.success = success
result.path = path
result.resource = resource
result.progress = progress
result.last = _open.is_empty()
_callback.call(result)

View File

@@ -0,0 +1,33 @@
@tool
var _file_system:EditorFileSystem
func _init(file_system:EditorFileSystem) -> void:
_file_system = file_system
## The resource group scanner finds all resource groups currently
## in the project.
## Scans the whole project for resources that match the
## group definition.
func scan() -> Array[Resource]:
var result:Array[Resource] = []
_scan(_file_system.get_filesystem(), result)
return result
func _scan(folder:EditorFileSystemDirectory, results:Array[Resource]):
# get all files in the folder
for i in folder.get_file_count():
if folder.get_file_type(i) == "Resource":
var path = folder.get_file_path(i)
if path.ends_with(".tres"):
var resource = ResourceLoader.load(path)
if resource is ResourceGroup:
results.append(resource)
# for each file first check if it matches the group definition, before trying to load it
for j in folder.get_subdir_count():
_scan(folder.get_subdir(j), results)

View File

@@ -0,0 +1,61 @@
@tool
const PathVerifier = preload("path_verifier.gd")
## The resource scanner scans the project for resources matching the given
## definition and returns a list of matching resources.
var _group:ResourceGroup
var _verifier:PathVerifier
var _file_system:EditorFileSystem
## Ctor.
func _init(group:ResourceGroup, file_system:EditorFileSystem):
_group = group
_verifier = PathVerifier.new(_group.base_folder, _group.includes, _group.excludes)
_file_system = file_system
## Scans the whole project for resources that match the
## group definition.
func scan() -> Array[String]:
var result:Array[String] = []
var folder = _group.base_folder
if folder == "":
push_warning("In resource group '" + _group.resource_path + "': Base folder is not set. Resource group will be empty.")
return result
var root = _file_system.get_filesystem_path(folder)
if root == null:
push_warning("In resource group '" + _group.resource_path + "': Base folder '" + folder + "' does not exist. Resource group will be empty.")
return result
_scan(root, result)
return result
func _scan(folder:EditorFileSystemDirectory, results:Array[String]):
# get all files in the folder
# for each file first check if it matches the group definition, before trying to load it
for i in folder.get_file_count():
var full_name = folder.get_file_path(i)
if _matches_group_definition(full_name):
if ResourceLoader.exists(full_name):
results.append(full_name)
else:
push_warning("In resource group '" + _group.resource_path + "': File '" + full_name + "' exists, but is not a supported Godot resource. It will be ignored.")
# recurse into subfolders
for j in folder.get_subdir_count():
_scan(folder.get_subdir(j), results)
func _matches_group_definition(file:String) -> bool:
# Skip import files
if file.ends_with(".import"):
return false
return _verifier.matches(file)