forked from joey/godottest
152 lines
5.7 KiB
GDScript
152 lines
5.7 KiB
GDScript
extends RefCounted
|
|
## This class is not thread safe.
|
|
## Do not call install, remove or load at the same time.
|
|
## Any texture loading has to be done on the main thread: https://github.com/godotengine/godot/issues/86796
|
|
|
|
const Collection := preload("res://addons/icon_explorer/internal/scripts/collection.gd")
|
|
const Icon := preload("res://addons/icon_explorer/internal/scripts/icon.gd")
|
|
|
|
signal collection_installed(id: int, status: Error)
|
|
signal collection_removed(id: int, status: Error)
|
|
signal load_started()
|
|
signal load_finished()
|
|
|
|
var _loaded_collections: Array[int] = []
|
|
var _collections: Array[Collection] = []
|
|
var _icons: Array[Icon] = []
|
|
|
|
var _scene_tree: SceneTree
|
|
|
|
## no mutex required as loading is suspended while main thread runs
|
|
## as this progress is exclusively used in loading the textures
|
|
var _load_progress: float
|
|
var _processing_thread: Thread
|
|
|
|
func load_progress() -> float:
|
|
return self._load_progress
|
|
|
|
func _init(scene_tree: SceneTree = null) -> void:
|
|
self._scene_tree = scene_tree
|
|
for path: String in Collection.get_default_collection_paths():
|
|
self.register(load(path.path_join("/collection.gd")).new() as Collection)
|
|
|
|
func _notification(what: int):
|
|
if what == NOTIFICATION_PREDELETE:
|
|
if self._processing_thread != null:
|
|
self._processing_thread.wait_to_finish()
|
|
|
|
func collections() -> Array[Collection]:
|
|
return self._collections
|
|
|
|
func get_collection(id: int) -> Collection:
|
|
for coll: Collection in self._collections:
|
|
if coll.id() == id:
|
|
return coll
|
|
|
|
return null
|
|
|
|
func installed_collections() -> Array[Collection]:
|
|
return self._collections.filter(func(coll: Collection) -> bool: return coll.is_installed())
|
|
|
|
func icons() -> Array[Icon]:
|
|
return self._icons
|
|
|
|
# register each collection only once
|
|
func register(coll: Collection) -> void:
|
|
self._collections.append(coll)
|
|
coll._id = self._collections.size() - 1
|
|
|
|
func install(coll: Collection, http: HTTPRequest, version: String) -> void:
|
|
if self._processing_thread != null && self._processing_thread.is_alive():
|
|
return
|
|
if self._processing_thread != null:
|
|
self._processing_thread.wait_to_finish()
|
|
|
|
self._processing_thread = Thread.new()
|
|
self._processing_thread.start(self._install.bind(coll, http, version))
|
|
|
|
# THREAD FUNCTION
|
|
func _install(coll: Collection, http: HTTPRequest, version: String) -> void:
|
|
var status: Error = coll.install(http, version)
|
|
if status != Error.OK:
|
|
self._install_done.bind(coll.id(), status).call_deferred()
|
|
return
|
|
# remove loaded icons and unload collection, to load the new ones
|
|
if self._loaded_collections.has(coll.id()):
|
|
self._icons = self._icons.filter(func (icon: Icon) -> bool: return icon.collection.id() != coll.id())
|
|
self._loaded_collections.remove_at(self._loaded_collections.find(coll.id()))
|
|
self._load()
|
|
self._install_done.bind(coll.id(), status).call_deferred()
|
|
|
|
func _install_done(id: int, status: Error) -> void:
|
|
self.collection_installed.emit(id, status)
|
|
|
|
func remove(coll: Collection) -> void:
|
|
if self._processing_thread != null && self._processing_thread.is_alive():
|
|
return
|
|
if self._processing_thread != null:
|
|
self._processing_thread.wait_to_finish()
|
|
|
|
self._processing_thread = Thread.new()
|
|
self._processing_thread.start(self._remove.bind(coll))
|
|
|
|
# THREAD FUNCTION
|
|
func _remove(coll: Collection) -> void:
|
|
var status: Error = coll.remove()
|
|
self._remove_done.bind(coll.id(), status).call_deferred()
|
|
|
|
func _remove_done(id: int, status: Error) -> void:
|
|
self._icons = self._icons.filter(func (icon: Icon) -> bool: return icon.collection.id() != id)
|
|
self._loaded_collections.remove_at(self._loaded_collections.find(id))
|
|
self.collection_removed.emit(id, status)
|
|
|
|
func load() -> void:
|
|
if self._processing_thread != null && self._processing_thread.is_alive():
|
|
return
|
|
if self._processing_thread != null:
|
|
self._processing_thread.wait_to_finish()
|
|
|
|
self._processing_thread = Thread.new()
|
|
self.load_started.emit()
|
|
self._processing_thread.start(self._load)
|
|
|
|
# thread function
|
|
# does not call self.load_started.emit()
|
|
func _load() -> void:
|
|
var loaded_icons: Array[Icon] = []
|
|
var buffers: PackedStringArray = PackedStringArray()
|
|
self._load_progress = 0.0
|
|
for idx: int in range(self._collections.size()):
|
|
var coll: Collection = self._collections[idx]
|
|
if !self._loaded_collections.has(coll.id()) && coll.is_installed():
|
|
var res: Array = coll.load()
|
|
loaded_icons.append_array(res[0])
|
|
buffers.append_array(res[1])
|
|
self._loaded_collections.append(coll.id())
|
|
self._load_done.bind(loaded_icons, buffers).call_deferred()
|
|
|
|
func _load_done(loaded_icons: Array[Icon], buffers: PackedStringArray) -> void:
|
|
for idx: int in range(loaded_icons.size()):
|
|
if idx % 50 == 0:
|
|
self._load_progress = float(idx + 1) / loaded_icons.size() * 100.0
|
|
if self._scene_tree != null:
|
|
await self._scene_tree.process_frame
|
|
_load_texture(loaded_icons[idx], buffers[idx])
|
|
self._icons.append_array(loaded_icons)
|
|
self.load_finished.emit()
|
|
|
|
static func _load_texture(icon: Icon, buffer: String) -> void:
|
|
var img: Image = Image.new()
|
|
# scale texture to 128
|
|
var scale: float = 1.0
|
|
if icon.svg_size == Vector2.ZERO:
|
|
push_warning("icon %s has no size" % icon.icon_path)
|
|
else:
|
|
scale = Collection.TEXTURE_SIZE / max(icon.svg_size.x, icon.svg_size.y)
|
|
var success: int = img.load_svg_from_string(buffer, scale)
|
|
if success != OK:
|
|
push_warning("could not load '" + icon.icon_path + "'")
|
|
return
|
|
img.fix_alpha_edges()
|
|
icon.texture = ImageTexture.create_from_image(img)
|