Generate tilemap on enter key being pressed
@ -1,7 +1,6 @@
|
||||
[configuration]
|
||||
entry_symbol = "gdext_rust_init"
|
||||
compatibility_minimum = 4.1
|
||||
reloadable = true
|
||||
|
||||
[libraries]
|
||||
"android.debug" = "res://../rust/target/debug/godottest_rs.so"
|
||||
@ -66,5 +65,6 @@ reloadable = true
|
||||
"windows.editor.x86_64" = "res://../rust/target/x86_64-pc-windows-msvc/debug/godottest_rs.dll"
|
||||
|
||||
[icons]
|
||||
Player = "res://addons/rust/NodeRustFerris.svg"
|
||||
AsyncRuntime = "res://addons/rust/NodeRustFerris.svg"
|
||||
Player = "res://addons/rust/NodeRustFerris.svg"
|
||||
Mappy = "res://addons/rust/NodeRustFerris.svg"
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
[gd_resource type="InputEventKey" format=3 uid="uid://bafyb8y38ahfh"]
|
||||
|
||||
[resource]
|
||||
device = -1
|
||||
ctrl_pressed = true
|
||||
keycode = 32
|
||||
unicode = 32
|
||||
@ -1,110 +0,0 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
# Padding from the bottom when popped out
|
||||
var padding: int = 20
|
||||
|
||||
# Padding from the bottom when not popped out
|
||||
var bottompadding: int = 60
|
||||
|
||||
# The file system
|
||||
var FileDock: Object
|
||||
|
||||
# Toggle for when the file system is moved to bottom
|
||||
var filesBottom: bool = false
|
||||
|
||||
var newSize: Vector2
|
||||
var initialLoad: bool = false
|
||||
|
||||
var AssetDrawerShortcut: InputEventKey = InputEventKey.new()
|
||||
var showing: bool = false
|
||||
|
||||
func _enter_tree() -> void:
|
||||
# Add tool button to move shelf to editor bottom
|
||||
add_tool_menu_item("Files to Bottom", Callable(self, "FilesToBottom"))
|
||||
|
||||
# Get our file system
|
||||
FileDock = self.get_editor_interface().get_file_system_dock()
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
FilesToBottom()
|
||||
|
||||
# Prevent file tree from being shrunk on load
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
var file_split_container := FileDock.get_child(3) as SplitContainer
|
||||
file_split_container .split_offset = 175
|
||||
|
||||
# Get shortcut
|
||||
AssetDrawerShortcut = preload("res://addons/Asset_Drawer/AssetDrawerShortcut.tres")
|
||||
|
||||
#region show hide filesystem
|
||||
func _input(event: InputEvent) -> void:
|
||||
if (AssetDrawerShortcut.is_match(event) &&
|
||||
event.is_pressed() &&
|
||||
!event.is_echo()):
|
||||
if filesBottom == true:
|
||||
match showing:
|
||||
false:
|
||||
make_bottom_panel_item_visible(FileDock)
|
||||
showing = true
|
||||
true:
|
||||
print("hide")
|
||||
hide_bottom_panel()
|
||||
showing = false
|
||||
#endregion
|
||||
|
||||
func _exit_tree() -> void:
|
||||
remove_tool_menu_item("Files to Bottom")
|
||||
FilesToBottom()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
|
||||
newSize = FileDock.get_window().size
|
||||
|
||||
# Keeps the file system from being unusable in size
|
||||
if FileDock.get_window().name == "root" && filesBottom == false:
|
||||
FileDock.get_child(3).get_child(0).size.y = newSize.y - padding
|
||||
FileDock.get_child(3).get_child(1).size.y = newSize.y - padding
|
||||
return
|
||||
|
||||
# Adjust the size of the file system based on how far up
|
||||
# the drawer has been pulled
|
||||
if FileDock.get_window().name == "root" && filesBottom == true:
|
||||
newSize = FileDock.get_parent().size
|
||||
var editor = get_editor_interface()
|
||||
var editorsettings = editor.get_editor_settings()
|
||||
var fontsize: int = editorsettings.get_setting("interface/editor/main_font_size")
|
||||
var editorscale = EditorInterface.get_editor_scale()
|
||||
|
||||
FileDock.get_child(3).get_child(0).size.y = newSize.y - (fontsize * 2) - (bottompadding * EditorInterface.get_editor_scale())
|
||||
FileDock.get_child(3).get_child(1).size.y = newSize.y - (fontsize * 2) - (bottompadding * EditorInterface.get_editor_scale())
|
||||
return
|
||||
|
||||
# Keeps our systems sized when popped out
|
||||
if (FileDock.get_window().name != "root" && filesBottom == false):
|
||||
FileDock.get_window().min_size.y = 50
|
||||
FileDock.get_child(3).get_child(0).size.y = newSize.y - padding
|
||||
FileDock.get_child(3).get_child(1).size.y = newSize.y - padding
|
||||
|
||||
# Centers window on first pop
|
||||
if initialLoad == false:
|
||||
initialLoad = true
|
||||
var screenSize: Vector2 = DisplayServer.screen_get_size()
|
||||
FileDock.get_window().position = screenSize/2
|
||||
|
||||
return
|
||||
|
||||
# Moves the files between the bottom panel and the original dock
|
||||
func FilesToBottom() -> void:
|
||||
if filesBottom == true:
|
||||
remove_control_from_bottom_panel(FileDock)
|
||||
add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_BR, FileDock)
|
||||
filesBottom = false
|
||||
return
|
||||
|
||||
FileDock = self.get_editor_interface().get_file_system_dock()
|
||||
remove_control_from_docks(FileDock)
|
||||
add_control_to_bottom_panel(FileDock, "File System")
|
||||
filesBottom = true
|
||||
|
||||
|
||||
@ -1 +0,0 @@
|
||||
uid://b5fge81gtvo4b
|
||||
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Michael McGuire
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -1,7 +0,0 @@
|
||||
[plugin]
|
||||
|
||||
name="Asset Drawer"
|
||||
description="Converts the File dock to an Asset Drawer at the bottom of the editor."
|
||||
author="GlitchedCode"
|
||||
version=""
|
||||
script="FileSystem.gd"
|
||||
@ -1,212 +0,0 @@
|
||||
class_name GedisCore
|
||||
|
||||
# Core data buckets
|
||||
var _store: Dictionary = {}
|
||||
var _hashes: Dictionary = {}
|
||||
var _lists: Dictionary = {}
|
||||
var _sets: Dictionary = {}
|
||||
var _sorted_sets: Dictionary = {}
|
||||
var _expiry: Dictionary = {}
|
||||
|
||||
# Pub/Sub registries
|
||||
var _subscribers: Dictionary[String, Array] = {}
|
||||
var _psubscribers: Dictionary[String, Array] = {}
|
||||
var _gedis: Gedis
|
||||
|
||||
func _init(gedis_instance: Gedis) -> void:
|
||||
_gedis = gedis_instance
|
||||
|
||||
func _now() -> int:
|
||||
return _gedis._time_source.get_time()
|
||||
|
||||
func _delete_all_types_for_key(key: String) -> void:
|
||||
_store.erase(key)
|
||||
_hashes.erase(key)
|
||||
_lists.erase(key)
|
||||
_sets.erase(key)
|
||||
_sorted_sets.erase(key)
|
||||
_expiry.erase(key)
|
||||
|
||||
func _touch_type(key: String, type_bucket: Dictionary) -> void:
|
||||
# When a key is used for a new type, remove it from other types.
|
||||
if not type_bucket.has(key):
|
||||
_store.erase(key)
|
||||
_hashes.erase(key)
|
||||
_lists.erase(key)
|
||||
_sets.erase(key)
|
||||
_sorted_sets.erase(key)
|
||||
|
||||
func key_exists(key: String) -> bool:
|
||||
return _store.has(key) or _hashes.has(key) or _lists.has(key) or _sets.has(key) or _sorted_sets.has(key)
|
||||
|
||||
func _get_all_keys() -> Dictionary[String, bool]:
|
||||
var all: Dictionary[String, bool] = {}
|
||||
for k in _store.keys():
|
||||
all[str(k)] = true
|
||||
for k in _hashes.keys():
|
||||
all[str(k)] = true
|
||||
for k in _lists.keys():
|
||||
all[str(k)] = true
|
||||
for k in _sets.keys():
|
||||
all[str(k)] = true
|
||||
for k in _sorted_sets.keys():
|
||||
all[str(k)] = true
|
||||
return all
|
||||
|
||||
func flushall() -> void:
|
||||
_store.clear()
|
||||
_hashes.clear()
|
||||
_lists.clear()
|
||||
_sets.clear()
|
||||
_sorted_sets.clear()
|
||||
_expiry.clear()
|
||||
_subscribers.clear()
|
||||
_psubscribers.clear()
|
||||
|
||||
|
||||
func dump_all(options: Dictionary = {}) -> Dictionary:
|
||||
var state := {
|
||||
"store": _store.duplicate(true),
|
||||
"hashes": _hashes.duplicate(true),
|
||||
"lists": _lists.duplicate(true),
|
||||
"sets": _sets.duplicate(true),
|
||||
"sorted_sets": _sorted_sets.duplicate(true),
|
||||
"expiry": _expiry.duplicate(true),
|
||||
}
|
||||
|
||||
var include: Array = options.get("include", [])
|
||||
var exclude: Array = options.get("exclude", [])
|
||||
|
||||
if not include.is_empty():
|
||||
for bucket_name in state:
|
||||
var bucket: Dictionary = state[bucket_name]
|
||||
for key in bucket.keys():
|
||||
var keep = false
|
||||
for prefix in include:
|
||||
if key.begins_with(prefix):
|
||||
keep = true
|
||||
break
|
||||
if not keep:
|
||||
bucket.erase(key)
|
||||
|
||||
if not exclude.is_empty():
|
||||
for bucket_name in state:
|
||||
var bucket: Dictionary = state[bucket_name]
|
||||
for key in bucket.keys():
|
||||
for prefix in exclude:
|
||||
if key.begins_with(prefix):
|
||||
bucket.erase(key)
|
||||
break
|
||||
return state
|
||||
|
||||
|
||||
func restore_all(state: Dictionary) -> void:
|
||||
flushall()
|
||||
|
||||
_store = state.get("store", {})
|
||||
_hashes = state.get("hashes", {})
|
||||
_lists = state.get("lists", {})
|
||||
_sets = state.get("sets", {})
|
||||
_sorted_sets = state.get("sorted_sets", {})
|
||||
_expiry = state.get("expiry", {})
|
||||
|
||||
# Discard expired keys
|
||||
var now: float = _now()
|
||||
for key in _expiry.keys():
|
||||
if _expiry[key] < now:
|
||||
_delete_all_types_for_key(key)
|
||||
|
||||
func restore_key(key: String, data: Dictionary) -> void:
|
||||
_delete_all_types_for_key(key)
|
||||
var value = data["value"]
|
||||
var type = data["type"]
|
||||
|
||||
if type == "string":
|
||||
_store[key] = value
|
||||
elif type == "hash":
|
||||
_hashes[key] = value
|
||||
elif type == "list":
|
||||
_lists[key] = value
|
||||
elif type == "set":
|
||||
_sets[key] = value
|
||||
elif type == "sorted_set":
|
||||
_sorted_sets[key] = value
|
||||
|
||||
if data.has("expiry"):
|
||||
_expiry[key] = data["expiry"]
|
||||
|
||||
func rename(key: String, newkey: String) -> int:
|
||||
if not key_exists(key):
|
||||
return ERR_DOES_NOT_EXIST
|
||||
|
||||
if key_exists(newkey):
|
||||
return 0
|
||||
|
||||
var value
|
||||
if _store.has(key):
|
||||
value = _store[key]
|
||||
_store[newkey] = value
|
||||
elif _hashes.has(key):
|
||||
value = _hashes[key]
|
||||
_hashes[newkey] = value
|
||||
elif _lists.has(key):
|
||||
value = _lists[key]
|
||||
_lists[newkey] = value
|
||||
elif _sets.has(key):
|
||||
value = _sets[key]
|
||||
_sets[newkey] = value
|
||||
elif _sorted_sets.has(key):
|
||||
value = _sorted_sets[key]
|
||||
_sorted_sets[newkey] = value
|
||||
else:
|
||||
return ERR_DOES_NOT_EXIST
|
||||
|
||||
if _expiry.has(key):
|
||||
var expiry_time = _expiry[key]
|
||||
_expiry[newkey] = expiry_time
|
||||
|
||||
_delete_all_types_for_key(key)
|
||||
return 1
|
||||
|
||||
func move(key: String, newkey: String) -> int:
|
||||
if not key_exists(key):
|
||||
return ERR_DOES_NOT_EXIST
|
||||
|
||||
if key_exists(newkey):
|
||||
_delete_all_types_for_key(newkey)
|
||||
|
||||
var value
|
||||
if _store.has(key):
|
||||
value = _store[key]
|
||||
_store[newkey] = value
|
||||
elif _hashes.has(key):
|
||||
value = _hashes[key]
|
||||
_hashes[newkey] = value
|
||||
elif _lists.has(key):
|
||||
value = _lists[key]
|
||||
_lists[newkey] = value
|
||||
elif _sets.has(key):
|
||||
value = _sets[key]
|
||||
_sets[newkey] = value
|
||||
elif _sorted_sets.has(key):
|
||||
value = _sorted_sets[key]
|
||||
_sorted_sets[newkey] = value
|
||||
else:
|
||||
return ERR_DOES_NOT_EXIST
|
||||
|
||||
if _expiry.has(key):
|
||||
var expiry_time = _expiry[key]
|
||||
_expiry[newkey] = expiry_time
|
||||
|
||||
_delete_all_types_for_key(key)
|
||||
return 1
|
||||
|
||||
func ks(key: String) -> String:
|
||||
return "gedis:keyspace:" + key
|
||||
|
||||
func rks(key: String) -> String:
|
||||
var prefix = "gedis:keyspace:"
|
||||
if key.begins_with(prefix):
|
||||
return key.substr(prefix.length())
|
||||
else:
|
||||
return key
|
||||
@ -1 +0,0 @@
|
||||
uid://ccfoujiaf8wpi
|
||||
@ -1,162 +0,0 @@
|
||||
extends RefCounted
|
||||
class_name GedisDebugger
|
||||
|
||||
var _gedis: Gedis
|
||||
|
||||
func _init(gedis: Gedis):
|
||||
_gedis = gedis
|
||||
if _gedis and _gedis._pubsub:
|
||||
_gedis._pubsub.subscribed.connect(_on_subscribed)
|
||||
_gedis._pubsub.unsubscribed.connect(_on_unsubscribed)
|
||||
|
||||
func _on_subscribed(channel: String, subscriber: Object):
|
||||
var debugger = Engine.get_singleton("EngineDebugger")
|
||||
if debugger and debugger.is_active():
|
||||
debugger.send_message("gedis:pubsub_event", ["subscribed", channel, str(subscriber)])
|
||||
|
||||
func _on_unsubscribed(channel: String, subscriber: Object):
|
||||
var debugger = Engine.get_singleton("EngineDebugger")
|
||||
if debugger and debugger.is_active():
|
||||
debugger.send_message("gedis:pubsub_event", ["unsubscribed", channel, str(subscriber)])
|
||||
|
||||
static func _ensure_debugger_is_registered():
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
if not Gedis._debugger_registered:
|
||||
if Engine.has_singleton("EngineDebugger"):
|
||||
var debugger = Engine.get_singleton("EngineDebugger")
|
||||
debugger.register_message_capture("gedis", Callable(Gedis, "_on_debugger_message"))
|
||||
if debugger.is_active():
|
||||
debugger.send_message("gedis:ping", [])
|
||||
Gedis._debugger_registered = true
|
||||
|
||||
static func _on_debugger_message(message: String, data: Array) -> bool:
|
||||
# EngineDebugger will call this with the suffix (the part after "gedis:")
|
||||
# so message will be e.g. "request_instances" or "request_instance_data".
|
||||
if not Engine.has_singleton("EngineDebugger"):
|
||||
return false
|
||||
|
||||
match message:
|
||||
"request_instances":
|
||||
var instances_data = []
|
||||
for instance_info in Gedis.get_all_instances():
|
||||
instances_data.append({
|
||||
"id": instance_info["id"],
|
||||
"name": instance_info["name"]
|
||||
})
|
||||
var debugger = Engine.get_singleton("EngineDebugger")
|
||||
if debugger and debugger.is_active():
|
||||
debugger.send_message("gedis:instances_data", instances_data)
|
||||
return true
|
||||
|
||||
"request_instance_data":
|
||||
if data.size() < 2:
|
||||
return false
|
||||
var instance_id = data[0]
|
||||
var command = data[1]
|
||||
|
||||
# Find the target instance in the static registry.
|
||||
var target_instance = null
|
||||
for inst in Gedis._instances:
|
||||
if is_instance_valid(inst) and inst._instance_id == instance_id:
|
||||
target_instance = inst
|
||||
break
|
||||
|
||||
if target_instance == null:
|
||||
return false
|
||||
|
||||
var debugger = Engine.get_singleton("EngineDebugger")
|
||||
if not debugger or not debugger.is_active():
|
||||
return false
|
||||
|
||||
match command:
|
||||
"snapshot":
|
||||
var pattern = data[2] if data.size() > 2 else "*"
|
||||
var snapshot_data = target_instance.snapshot(pattern)
|
||||
debugger.send_message("gedis:snapshot_data", [snapshot_data])
|
||||
return true
|
||||
"dump":
|
||||
if data.size() < 3:
|
||||
return false
|
||||
var key = data[2]
|
||||
var key_value_data = target_instance.dump_key(key)
|
||||
debugger.send_message("gedis:key_value_data", [key_value_data])
|
||||
return true
|
||||
"set":
|
||||
if data.size() < 4:
|
||||
return false
|
||||
var key = data[2]
|
||||
var value = data[3]
|
||||
target_instance.set_value(key, value)
|
||||
var key_value_data = target_instance.dump_key(key)
|
||||
debugger.send_message("gedis:key_value_data", [key_value_data])
|
||||
return true
|
||||
"pubsub":
|
||||
var channels = target_instance._pubsub.list_channels()
|
||||
var channels_data = {}
|
||||
for channel in channels:
|
||||
channels_data[channel] = target_instance._pubsub.list_subscribers(channel)
|
||||
|
||||
var patterns = target_instance._pubsub.list_patterns()
|
||||
var patterns_data = {}
|
||||
for pattern in patterns:
|
||||
patterns_data[pattern] = target_instance._pubsub.list_pattern_subscribers(pattern)
|
||||
|
||||
debugger.send_message("gedis:pubsub_data", [channels_data, patterns_data])
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
# Debugger-like helpers: type/dump/snapshot
|
||||
func type(key: String) -> String:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return "none"
|
||||
if _gedis._core._store.has(key):
|
||||
return "string"
|
||||
if _gedis._core._hashes.has(key):
|
||||
return "hash"
|
||||
if _gedis._core._lists.has(key):
|
||||
return "list"
|
||||
if _gedis._core._sets.has(key):
|
||||
return "set"
|
||||
if _gedis._core._sorted_sets.has(key):
|
||||
return "zset"
|
||||
return "none"
|
||||
|
||||
func dump(key: String) -> Dictionary:
|
||||
var t = type(key)
|
||||
if t == "none":
|
||||
return {}
|
||||
var d: Dictionary = {}
|
||||
d["type"] = t
|
||||
d["ttl"] = _gedis.ttl(key)
|
||||
match t:
|
||||
"string":
|
||||
d["value"] = _gedis._core._store.get(key, null)
|
||||
"hash":
|
||||
d["value"] = _gedis._core._hashes.get(key, {}).duplicate(true)
|
||||
"list":
|
||||
d["value"] = _gedis._core._lists.get(key, []).duplicate()
|
||||
"set":
|
||||
d["value"] = _gedis._core._sets.get(key, {}).keys()
|
||||
"zset":
|
||||
var data = _gedis._core._sorted_sets.get(key, {})
|
||||
var value = []
|
||||
if data.has("sorted_set"):
|
||||
# The internal representation is [score, member] but for visualization
|
||||
# it's more intuitive to show [member, score].
|
||||
for entry in data.sorted_set:
|
||||
value.append([entry[1], entry[0]])
|
||||
d["value"] = value
|
||||
_:
|
||||
d["value"] = null
|
||||
return d
|
||||
|
||||
func snapshot(pattern: String = "*") -> Dictionary:
|
||||
var out: Dictionary = {}
|
||||
for k in _gedis._strings.keys(pattern):
|
||||
var key_data = dump(str(k))
|
||||
key_data["ttl"] = _gedis._expiry.ttl(str(k))
|
||||
out[str(k)] = key_data
|
||||
return out
|
||||
@ -1 +0,0 @@
|
||||
uid://bx77jybsq60mc
|
||||
@ -1,98 +0,0 @@
|
||||
extends RefCounted
|
||||
class_name GedisExpiry
|
||||
|
||||
var _gedis: Gedis
|
||||
|
||||
# Provides an ordered view of expirations for efficient purging.
|
||||
# The list is sorted by timestamp and stores [timestamp, key] pairs.
|
||||
var _expiry_list: Array
|
||||
|
||||
func _init(p_gedis: Gedis):
|
||||
_gedis = p_gedis
|
||||
_expiry_list = []
|
||||
|
||||
func _now() -> int:
|
||||
return _gedis._time_source.get_time()
|
||||
|
||||
func _is_expired(key: String) -> bool:
|
||||
if _gedis._core._expiry.has(key) and _gedis._core._expiry[key] <= _now():
|
||||
var timestamp = _gedis._core._expiry[key]
|
||||
_gedis._core._delete_all_types_for_key(key)
|
||||
var entry = [timestamp, key]
|
||||
var index = _expiry_list.bsearch(entry)
|
||||
if index < _expiry_list.size() and _expiry_list[index] == entry:
|
||||
_expiry_list.remove_at(index)
|
||||
return true
|
||||
return false
|
||||
|
||||
func _purge_expired() -> void:
|
||||
var now := _now()
|
||||
var expired_count := 0
|
||||
var ex := _gedis._core._expiry
|
||||
for item in _expiry_list:
|
||||
var timestamp: int = item[0]
|
||||
var key: String = item[1]
|
||||
|
||||
if timestamp <= now:
|
||||
if ex.has(key) and ex[key] == timestamp:
|
||||
_gedis._core._delete_all_types_for_key(key)
|
||||
_gedis.publish("gedis:keyspace:" + key, "expire")
|
||||
expired_count += 1
|
||||
else:
|
||||
break
|
||||
|
||||
if expired_count > 0:
|
||||
_expiry_list = _expiry_list.slice(expired_count)
|
||||
|
||||
# ----------------
|
||||
# Expiry commands
|
||||
# ----------------
|
||||
func expire(key: String, seconds: int) -> bool:
|
||||
if not _gedis.exists(key):
|
||||
return false
|
||||
|
||||
# If the key already has an expiry, remove the old entry from the sorted list.
|
||||
if _gedis._core._expiry.has(key):
|
||||
var old_timestamp = _gedis._core._expiry[key]
|
||||
var old_entry = [old_timestamp, key]
|
||||
var index = _expiry_list.bsearch(old_entry)
|
||||
if index < _expiry_list.size() and _expiry_list[index] == old_entry:
|
||||
_expiry_list.remove_at(index)
|
||||
|
||||
var new_timestamp = _now() + (float(seconds) * 1000.0)
|
||||
_gedis._core._expiry[key] = new_timestamp
|
||||
|
||||
# Add the new expiry to the sorted list.
|
||||
var new_entry = [new_timestamp, key]
|
||||
var insertion_index = _expiry_list.bsearch(new_entry)
|
||||
_expiry_list.insert(insertion_index, new_entry)
|
||||
|
||||
return true
|
||||
|
||||
# TTL returns:
|
||||
# -2 if the key does not exist
|
||||
# -1 if the key exists but has no associated expire
|
||||
# >= 0 number of seconds to expire
|
||||
func ttl(key: String) -> int:
|
||||
if not _gedis.exists(key):
|
||||
return -2
|
||||
if not _gedis._core._expiry.has(key):
|
||||
return -1
|
||||
return max(0, int(ceil((_gedis._core._expiry[key] - _now()) / 1000.0)))
|
||||
|
||||
func persist(key: String) -> bool:
|
||||
if not _gedis.exists(key):
|
||||
return false
|
||||
if _gedis._core._expiry.has(key):
|
||||
var timestamp = _gedis._core._expiry[key]
|
||||
_gedis._core._expiry.erase(key)
|
||||
var entry = [timestamp, key]
|
||||
var index = _expiry_list.bsearch(entry)
|
||||
if index < _expiry_list.size() and _expiry_list[index] == entry:
|
||||
_expiry_list.remove_at(index)
|
||||
return true
|
||||
return false
|
||||
|
||||
func setex(key: String, seconds: int, value: Variant) -> void:
|
||||
_gedis.set_value(key, value)
|
||||
expire(key, seconds)
|
||||
@ -1 +0,0 @@
|
||||
uid://bscau2tkd4lcx
|
||||
@ -1,132 +0,0 @@
|
||||
extends RefCounted
|
||||
class_name GedisHashes
|
||||
|
||||
var _gedis
|
||||
|
||||
func _init(gedis):
|
||||
_gedis = gedis
|
||||
|
||||
# ------
|
||||
# Hashes
|
||||
# ------
|
||||
func hset(key: String, field: String, value) -> int:
|
||||
_gedis._core._touch_type(key, _gedis._core._hashes)
|
||||
var d: Dictionary = _gedis._core._hashes.get(key, {})
|
||||
var existed := int(d.has(field))
|
||||
d[field] = value
|
||||
_gedis._core._hashes[key] = d
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
return 1 - existed
|
||||
|
||||
func hget(key: String, field: String, default_value: Variant = null):
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return default_value
|
||||
var d: Dictionary = _gedis._core._hashes.get(key, {})
|
||||
return d.get(field, default_value)
|
||||
|
||||
# Gets the values of multiple fields in a hash.
|
||||
# ---
|
||||
# @param key: The key of the hash.
|
||||
# @param fields: An array of fields to get the values of.
|
||||
# @return: An array of values for the given fields. If a field does not exist, the corresponding value in the array will be null.
|
||||
func hmget(key: String, fields: Array) -> Array:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return fields.map(func(_field): return null)
|
||||
var d: Dictionary = _gedis._core._hashes.get(key, {})
|
||||
var result: Array = []
|
||||
for field in fields:
|
||||
result.append(d.get(field, null))
|
||||
return result
|
||||
|
||||
# Sets multiple fields and their values in a hash.
|
||||
# ---
|
||||
# @param key: The key of the hash.
|
||||
# @param field_value_pairs: A dictionary of field-value pairs to set.
|
||||
func hmset(key: String, field_value_pairs: Dictionary) -> void:
|
||||
_gedis._core._touch_type(key, _gedis._core._hashes)
|
||||
var d: Dictionary = _gedis._core._hashes.get(key, {})
|
||||
for field in field_value_pairs:
|
||||
d[field] = field_value_pairs[field]
|
||||
_gedis._core._hashes[key] = d
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
|
||||
func hincrby(key: String, field: String, amount: int) -> Variant:
|
||||
_gedis._core._touch_type(key, _gedis._core._hashes)
|
||||
var d: Dictionary = _gedis._core._hashes.get(key, {})
|
||||
var value = d.get(field, 0)
|
||||
if not typeof(value) in [TYPE_INT, TYPE_FLOAT]:
|
||||
push_error("WRONGTYPE Operation against a key holding the wrong kind of value")
|
||||
return null
|
||||
value += amount
|
||||
d[field] = value
|
||||
_gedis._core._hashes[key] = d
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
return value
|
||||
|
||||
func hincrbyfloat(key: String, field: String, amount: float) -> Variant:
|
||||
_gedis._core._touch_type(key, _gedis._core._hashes)
|
||||
var d: Dictionary = _gedis._core._hashes.get(key, {})
|
||||
var value = d.get(field, 0.0)
|
||||
if not typeof(value) in [TYPE_INT, TYPE_FLOAT]:
|
||||
push_error("WRONGTYPE Operation against a key holding the wrong kind of value")
|
||||
return null
|
||||
value += amount
|
||||
d[field] = value
|
||||
_gedis._core._hashes[key] = d
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
return value
|
||||
|
||||
func hdel(key: String, fields) -> int:
|
||||
# Accept single field (String) or Array of fields
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return 0
|
||||
if not _gedis._core._hashes.has(key):
|
||||
return 0
|
||||
var d: Dictionary = _gedis._core._hashes[key]
|
||||
var removed = 0
|
||||
if typeof(fields) == TYPE_ARRAY:
|
||||
for f in fields:
|
||||
if d.has(str(f)):
|
||||
d.erase(str(f))
|
||||
removed += 1
|
||||
else:
|
||||
var f = str(fields)
|
||||
if d.has(f):
|
||||
d.erase(f)
|
||||
removed = 1
|
||||
if d.is_empty():
|
||||
_gedis._core._hashes.erase(key)
|
||||
_gedis.publish("gedis:keyspace:" + key, "del")
|
||||
else:
|
||||
_gedis._core._hashes[key] = d
|
||||
return removed
|
||||
|
||||
func hgetall(key: String) -> Dictionary:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return {}
|
||||
return _gedis._core._hashes.get(key, {}).duplicate(true)
|
||||
|
||||
func hexists(key: String, field = null) -> bool:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return false
|
||||
|
||||
if field == null:
|
||||
return _gedis._core._hashes.has(key)
|
||||
|
||||
var d: Dictionary = _gedis._core._hashes.get(key, {})
|
||||
return d.has(field)
|
||||
|
||||
func hkeys(key: String) -> Array:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return []
|
||||
return _gedis._core._hashes.get(key, {}).keys()
|
||||
|
||||
func hvals(key: String) -> Array:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return []
|
||||
return _gedis._core._hashes.get(key, {}).values()
|
||||
|
||||
func hlen(key: String) -> int:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return 0
|
||||
return _gedis._core._hashes.get(key, {}).size()
|
||||
@ -1 +0,0 @@
|
||||
uid://dcvde42llr5d4
|
||||
@ -1,293 +0,0 @@
|
||||
extends RefCounted
|
||||
class_name GedisLists
|
||||
|
||||
var _gedis
|
||||
|
||||
func _init(gedis):
|
||||
_gedis = gedis
|
||||
|
||||
# -----
|
||||
# Lists
|
||||
# -----
|
||||
func lpush(key: String, value) -> int:
|
||||
_gedis._core._touch_type(key, _gedis._core._lists)
|
||||
var a: Array = _gedis._core._lists.get(key, [])
|
||||
if typeof(value) == TYPE_ARRAY:
|
||||
a = value + a
|
||||
else:
|
||||
a.insert(0, value)
|
||||
_gedis._core._lists[key] = a
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
return a.size()
|
||||
|
||||
func rpush(key: String, value) -> int:
|
||||
_gedis._core._touch_type(key, _gedis._core._lists)
|
||||
var a: Array = _gedis._core._lists.get(key, [])
|
||||
if typeof(value) == TYPE_ARRAY:
|
||||
a += value
|
||||
else:
|
||||
a.append(value)
|
||||
_gedis._core._lists[key] = a
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
return a.size()
|
||||
|
||||
func lpop(key: String):
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return null
|
||||
if not _gedis._core._lists.has(key):
|
||||
return null
|
||||
var a: Array = _gedis._core._lists[key].duplicate()
|
||||
if a.is_empty():
|
||||
return null
|
||||
var v = a.pop_front()
|
||||
if a.is_empty():
|
||||
_gedis._core._lists.erase(key)
|
||||
_gedis.publish("gedis:keyspace:" + key, "del")
|
||||
else:
|
||||
_gedis._core._lists[key] = a
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
return v
|
||||
|
||||
func rpop(key: String):
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return null
|
||||
if not _gedis._core._lists.has(key):
|
||||
return null
|
||||
var a: Array = _gedis._core._lists[key].duplicate()
|
||||
if a.is_empty():
|
||||
return null
|
||||
var v = a.pop_back()
|
||||
if a.is_empty():
|
||||
_gedis._core._lists.erase(key)
|
||||
_gedis.publish("gedis:keyspace:" + key, "del")
|
||||
else:
|
||||
_gedis._core._lists[key] = a
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
return v
|
||||
|
||||
func llen(key: String) -> int:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return 0
|
||||
var a: Array = _gedis._core._lists.get(key, [])
|
||||
return a.size()
|
||||
|
||||
func lexists(key: String) -> bool:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return false
|
||||
return _gedis._core._lists.has(key)
|
||||
|
||||
func lget(key: String) -> Array:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return []
|
||||
return _gedis._core._lists.get(key, []).duplicate()
|
||||
|
||||
func lrange(key: String, start: int, stop: int) -> Array:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return []
|
||||
var a: Array = _gedis._core._lists.get(key, [])
|
||||
var n = a.size()
|
||||
# normalize negative indices
|
||||
if start < 0:
|
||||
start = n + start
|
||||
if stop < 0:
|
||||
stop = n + stop
|
||||
# clamp
|
||||
start = max(0, start)
|
||||
stop = min(n - 1, stop)
|
||||
if start > stop or n == 0:
|
||||
return []
|
||||
var out: Array = []
|
||||
for i in range(start, stop + 1):
|
||||
out.append(a[i])
|
||||
return out
|
||||
|
||||
func lindex(key: String, index: int):
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return null
|
||||
var a: Array = _gedis._core._lists.get(key, [])
|
||||
var n = a.size()
|
||||
if n == 0:
|
||||
return null
|
||||
if index < 0:
|
||||
index = n + index
|
||||
if index < 0 or index >= n:
|
||||
return null
|
||||
return a[index]
|
||||
|
||||
func lset(key: String, index: int, value) -> bool:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return false
|
||||
if not _gedis._core._lists.has(key):
|
||||
return false
|
||||
var a: Array = _gedis._core._lists[key].duplicate()
|
||||
var n = a.size()
|
||||
if index < 0:
|
||||
index = n + index
|
||||
if index < 0 or index >= n:
|
||||
return false
|
||||
a[index] = value
|
||||
_gedis._core._lists[key] = a
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
return true
|
||||
|
||||
func lrem(key: String, count: int, value) -> int:
|
||||
# Remove elements equal to value. Behavior similar to Redis.
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return 0
|
||||
if not _gedis._core._lists.has(key):
|
||||
return 0
|
||||
var a: Array = _gedis._core._lists[key].duplicate()
|
||||
var removed = 0
|
||||
if count == 0:
|
||||
# remove all
|
||||
var filtered: Array = []
|
||||
for v in a:
|
||||
if v == value:
|
||||
removed += 1
|
||||
else:
|
||||
filtered.append(v)
|
||||
a = filtered
|
||||
elif count > 0:
|
||||
var out: Array = []
|
||||
for v in a:
|
||||
if v == value and removed < count:
|
||||
removed += 1
|
||||
continue
|
||||
out.append(v)
|
||||
a = out
|
||||
else:
|
||||
# count < 0, remove from tail
|
||||
var rev = a.duplicate()
|
||||
rev.reverse()
|
||||
var out2: Array = []
|
||||
for v in rev:
|
||||
if v == value and removed < abs(count):
|
||||
removed += 1
|
||||
continue
|
||||
out2.append(v)
|
||||
out2.reverse()
|
||||
a = out2
|
||||
if a.is_empty():
|
||||
_gedis._core._lists.erase(key)
|
||||
_gedis.publish("gedis:keyspace:" + key, "del")
|
||||
else:
|
||||
_gedis._core._lists[key] = a
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
return removed
|
||||
|
||||
func lmove(source: String, destination: String, from: String, to: String):
|
||||
if _gedis._expiry._is_expired(source):
|
||||
return null
|
||||
if not _gedis._core._lists.has(source):
|
||||
return null
|
||||
|
||||
var source_list: Array = _gedis._core._lists[source]
|
||||
if source_list.is_empty():
|
||||
return null
|
||||
|
||||
var element
|
||||
if from.to_upper() == "LEFT":
|
||||
element = source_list.pop_front()
|
||||
elif from.to_upper() == "RIGHT":
|
||||
element = source_list.pop_back()
|
||||
else:
|
||||
return null
|
||||
|
||||
_gedis._core._touch_type(destination, _gedis._core._lists)
|
||||
var dest_list: Array = _gedis._core._lists.get(destination, [])
|
||||
|
||||
if to.to_upper() == "LEFT":
|
||||
dest_list.insert(0, element)
|
||||
elif to.to_upper() == "RIGHT":
|
||||
dest_list.append(element)
|
||||
else:
|
||||
# Invalid 'to', restore source list and return error
|
||||
if from.to_upper() == "LEFT":
|
||||
source_list.insert(0, element)
|
||||
else:
|
||||
source_list.append(element)
|
||||
return null
|
||||
|
||||
if source_list.is_empty():
|
||||
_gedis._core._lists.erase(source)
|
||||
_gedis.publish("gedis:keyspace:" + source, "del")
|
||||
else:
|
||||
_gedis._core._lists[source] = source_list
|
||||
_gedis.publish("gedis:keyspace:" + source, "set")
|
||||
_gedis._core._lists[destination] = dest_list
|
||||
_gedis.publish("gedis:keyspace:" + destination, "set")
|
||||
|
||||
return element
|
||||
# Trims a list to the specified range of indices.
|
||||
# Removes all elements from the list that are not in the range [start, stop].
|
||||
# If start is greater than stop, or start is out of bounds, the list will be emptied.
|
||||
# ---
|
||||
# @param key: The key of the list to trim.
|
||||
# @param start: The starting index of the range.
|
||||
# @param stop: The ending index of the range.
|
||||
# @return: Returns true if the list was trimmed, false otherwise.
|
||||
func ltrim(key: String, start: int, stop: int) -> bool:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return false
|
||||
if not _gedis._core._lists.has(key):
|
||||
return false
|
||||
|
||||
var a: Array = _gedis._core._lists[key].duplicate()
|
||||
var n = a.size()
|
||||
|
||||
if start < 0:
|
||||
start = n + start
|
||||
if stop < 0:
|
||||
stop = n + stop
|
||||
|
||||
start = max(0, start)
|
||||
stop = min(n - 1, stop)
|
||||
|
||||
if start > stop:
|
||||
_gedis._core._lists.erase(key)
|
||||
_gedis.publish("gedis:keyspace:" + key, "del")
|
||||
return true
|
||||
|
||||
var trimmed_list: Array = []
|
||||
for i in range(start, stop + 1):
|
||||
trimmed_list.append(a[i])
|
||||
|
||||
_gedis._core._lists[key] = trimmed_list
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
return true
|
||||
|
||||
# Inserts a value into a list before or after a pivot value.
|
||||
# ---
|
||||
# @param key: The key of the list.
|
||||
# @param position: "BEFORE" or "AFTER" the pivot.
|
||||
# @param pivot: The value to insert the new value next to.
|
||||
# @param value: The value to insert.
|
||||
# @return: The new size of the list, or -1 if the pivot was not found. Returns 0 if the key does not exist or the position is invalid.
|
||||
func linsert(key: String, position: String, pivot: Variant, value: Variant) -> int:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return 0
|
||||
if not _gedis._core._lists.has(key):
|
||||
return 0
|
||||
|
||||
var a: Array = _gedis._core._lists[key].duplicate()
|
||||
var n = a.size()
|
||||
var index = -1
|
||||
|
||||
for i in range(n):
|
||||
if a[i] == pivot:
|
||||
index = i
|
||||
break
|
||||
|
||||
if index == -1:
|
||||
return -1
|
||||
|
||||
if position.to_upper() == "BEFORE":
|
||||
a.insert(index, value)
|
||||
elif position.to_upper() == "AFTER":
|
||||
a.insert(index + 1, value)
|
||||
else:
|
||||
return 0
|
||||
|
||||
_gedis._core._lists[key] = a
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
return a.size()
|
||||
@ -1 +0,0 @@
|
||||
uid://budmyot3vslr3
|
||||
@ -1,106 +0,0 @@
|
||||
extends RefCounted
|
||||
class_name GedisPubSub
|
||||
|
||||
var _gedis: Gedis
|
||||
|
||||
signal pubsub_message(channel, message)
|
||||
signal psub_message(pattern, channel, message)
|
||||
|
||||
signal subscribed(channel, subscriber)
|
||||
signal unsubscribed(channel, subscriber)
|
||||
|
||||
func _init(gedis: Gedis):
|
||||
_gedis = gedis
|
||||
|
||||
# --------
|
||||
# Pub/Sub
|
||||
# --------
|
||||
func publish(channel: String, message) -> void:
|
||||
# Backwards-compatible delivery:
|
||||
# 1) If subscriber objects registered via subscribe/psubscribe expect direct signals,
|
||||
# call their 'pubsub_message'/'psub_message' on the subscriber object.
|
||||
# 2) Emit a single Gedis-level signal so external code can connect to this Gedis instance.
|
||||
# This avoids emitting the same Gedis signal multiple times (which would cause duplicate callbacks).
|
||||
# Direct subscribers (back-compat)
|
||||
if _gedis._core._subscribers.has(channel):
|
||||
for subscriber in _gedis._core._subscribers[channel]:
|
||||
if is_instance_valid(subscriber):
|
||||
# deliver directly to subscriber object if it exposes the signal
|
||||
if subscriber.has_signal("pubsub_message"):
|
||||
subscriber.pubsub_message.emit(channel, message)
|
||||
# Emit a single Gedis-level pubsub notification for all listeners connected to this Gedis instance.
|
||||
if _gedis._core._subscribers.has(channel) and _gedis._core._subscribers[channel].size() > 0:
|
||||
pubsub_message.emit(channel, message)
|
||||
# Pattern subscribers (back-compat + Gedis-level)
|
||||
for pattern in _gedis._core._psubscribers.keys():
|
||||
# Use simple glob matching: convert to RegEx
|
||||
var rx = _gedis._utils._glob_to_regex(pattern)
|
||||
if rx.search(channel) != null:
|
||||
for subscriber in _gedis._core._psubscribers[pattern]:
|
||||
if is_instance_valid(subscriber):
|
||||
if subscriber.has_signal("psub_message"):
|
||||
subscriber.psub_message.emit(pattern, channel, message)
|
||||
# Emit one Gedis-level pattern message for this matching pattern
|
||||
psub_message.emit(pattern, channel, message)
|
||||
|
||||
func subscribe(channel: String, subscriber: Object) -> void:
|
||||
var arr: Array = _gedis._core._subscribers.get(channel, [])
|
||||
# avoid duplicates
|
||||
for s in arr:
|
||||
if s == subscriber:
|
||||
return
|
||||
arr.append(subscriber)
|
||||
_gedis._core._subscribers[channel] = arr
|
||||
subscribed.emit(channel, subscriber)
|
||||
|
||||
func unsubscribe(channel: String, subscriber: Object) -> void:
|
||||
if not _gedis._core._subscribers.has(channel):
|
||||
return
|
||||
var arr: Array = _gedis._core._subscribers[channel]
|
||||
for i in range(arr.size()):
|
||||
if arr[i] == subscriber:
|
||||
arr.remove_at(i)
|
||||
unsubscribed.emit(channel, subscriber)
|
||||
break
|
||||
if arr.is_empty():
|
||||
_gedis._core._subscribers.erase(channel)
|
||||
else:
|
||||
_gedis._core._subscribers[channel] = arr
|
||||
|
||||
func psubscribe(pattern: String, subscriber: Object) -> void:
|
||||
var arr: Array = _gedis._core._psubscribers.get(pattern, [])
|
||||
for s in arr:
|
||||
if s == subscriber:
|
||||
return
|
||||
arr.append(subscriber)
|
||||
_gedis._core._psubscribers[pattern] = arr
|
||||
subscribed.emit(pattern, subscriber)
|
||||
|
||||
func punsubscribe(pattern: String, subscriber: Object) -> void:
|
||||
if not _gedis._core._psubscribers.has(pattern):
|
||||
return
|
||||
var arr: Array = _gedis._core._psubscribers[pattern]
|
||||
for i in range(arr.size()):
|
||||
if arr[i] == subscriber:
|
||||
arr.remove_at(i)
|
||||
unsubscribed.emit(pattern, subscriber)
|
||||
break
|
||||
if arr.is_empty():
|
||||
_gedis._core._psubscribers.erase(pattern)
|
||||
else:
|
||||
_gedis._core._psubscribers[pattern] = arr
|
||||
|
||||
# -----------
|
||||
# Introspection
|
||||
# -----------
|
||||
func list_channels() -> Array:
|
||||
return _gedis._core._subscribers.keys()
|
||||
|
||||
func list_subscribers(channel: String) -> Array:
|
||||
return _gedis._core._subscribers.get(channel, [])
|
||||
|
||||
func list_patterns() -> Array:
|
||||
return _gedis._core._psubscribers.keys()
|
||||
|
||||
func list_pattern_subscribers(pattern: String) -> Array:
|
||||
return _gedis._core._psubscribers.get(pattern, [])
|
||||
@ -1 +0,0 @@
|
||||
uid://jwecqfjo870q
|
||||
@ -1,216 +0,0 @@
|
||||
extends RefCounted
|
||||
class_name GedisSets
|
||||
|
||||
var _gedis: Gedis
|
||||
|
||||
func _init(gedis: Gedis):
|
||||
_gedis = gedis
|
||||
|
||||
# ----
|
||||
# Sets
|
||||
# ----
|
||||
func sadd(key: String, member) -> int:
|
||||
_gedis._core._touch_type(key, _gedis._core._sets)
|
||||
var s: Dictionary = _gedis._core._sets.get(key, {})
|
||||
var existed := int(s.has(member))
|
||||
s[member] = true
|
||||
_gedis._core._sets[key] = s
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
return 1 - existed
|
||||
|
||||
func srem(key: String, member) -> int:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return 0
|
||||
if not _gedis._core._sets.has(key):
|
||||
return 0
|
||||
var s: Dictionary = _gedis._core._sets[key]
|
||||
var existed := int(s.has(member))
|
||||
s.erase(member)
|
||||
if s.is_empty():
|
||||
_gedis._core._sets.erase(key)
|
||||
_gedis.publish("gedis:keyspace:" + key, "del")
|
||||
else:
|
||||
_gedis._core._sets[key] = s
|
||||
return existed
|
||||
|
||||
func smembers(key: String) -> Array:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return []
|
||||
var s: Dictionary = _gedis._core._sets.get(key, {})
|
||||
return s.keys()
|
||||
|
||||
func sismember(key: String, member) -> bool:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return false
|
||||
var s: Dictionary = _gedis._core._sets.get(key, {})
|
||||
return s.has(member)
|
||||
|
||||
func scard(key: String) -> int:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return 0
|
||||
return _gedis._core._sets.get(key, {}).size()
|
||||
|
||||
func sexists(key: String) -> bool:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return false
|
||||
return _gedis._core._sets.has(key)
|
||||
|
||||
func spop(key: String):
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return null
|
||||
if not _gedis._core._sets.has(key):
|
||||
return null
|
||||
var s: Dictionary = _gedis._core._sets[key]
|
||||
var keys_arr: Array = s.keys()
|
||||
if keys_arr.is_empty():
|
||||
return null
|
||||
var idx = randi() % keys_arr.size()
|
||||
var member = keys_arr[idx]
|
||||
s.erase(member)
|
||||
if s.is_empty():
|
||||
_gedis._core._sets.erase(key)
|
||||
_gedis.publish("gedis:keyspace:" + key, "del")
|
||||
else:
|
||||
_gedis._core._sets[key] = s
|
||||
return member
|
||||
|
||||
func smove(source: String, destination: String, member) -> bool:
|
||||
if _gedis._expiry._is_expired(source):
|
||||
return false
|
||||
if not sismember(source, member):
|
||||
return false
|
||||
# remove from source
|
||||
srem(source, member)
|
||||
# add to destination (creates destination set)
|
||||
sadd(destination, member)
|
||||
return true
|
||||
|
||||
# Computes the union of multiple sets.
|
||||
# ---
|
||||
# @param keys: An array of set keys.
|
||||
# @return: An array containing the members of the resulting union set.
|
||||
func sunion(keys: Array) -> Array:
|
||||
var result_set := {}
|
||||
for key in keys:
|
||||
var members = smembers(key)
|
||||
for member in members:
|
||||
result_set[member] = true
|
||||
return result_set.keys()
|
||||
|
||||
# Computes the intersection of multiple sets.
|
||||
# ---
|
||||
# @param keys: An array of set keys.
|
||||
# @return: An array containing the members of the resulting intersection set.
|
||||
func sinter(keys: Array) -> Array:
|
||||
if keys.is_empty():
|
||||
return []
|
||||
|
||||
var result_set := {}
|
||||
var first_set_members = smembers(keys[0])
|
||||
for member in first_set_members:
|
||||
result_set[member] = true
|
||||
|
||||
for i in range(1, keys.size()):
|
||||
var next_set_members = smembers(keys[i])
|
||||
var current_members = result_set.keys()
|
||||
for member in current_members:
|
||||
if not next_set_members.has(member):
|
||||
result_set.erase(member)
|
||||
|
||||
return result_set.keys()
|
||||
|
||||
# Computes the difference between multiple sets.
|
||||
# The difference is calculated as the members of the first set minus the members of all subsequent sets.
|
||||
# ---
|
||||
# @param keys: An array of set keys. The first key is the set to subtract from.
|
||||
# @return: An array containing the members of the resulting difference set.
|
||||
func sdiff(keys: Array) -> Array:
|
||||
if keys.is_empty():
|
||||
return []
|
||||
|
||||
var result_set := {}
|
||||
var first_set_members = smembers(keys[0])
|
||||
for member in first_set_members:
|
||||
result_set[member] = true
|
||||
|
||||
for i in range(1, keys.size()):
|
||||
var next_set_members = smembers(keys[i])
|
||||
for member in next_set_members:
|
||||
if result_set.has(member):
|
||||
result_set.erase(member)
|
||||
|
||||
return result_set.keys()
|
||||
|
||||
# Computes the union of multiple sets and stores the result in a new set.
|
||||
# ---
|
||||
# @param destination: The key to store the resulting union set in.
|
||||
# @param keys: An array of set keys.
|
||||
# @return: The number of members in the resulting union set.
|
||||
func sunionstore(destination: String, keys: Array) -> int:
|
||||
var result_members = sunion(keys)
|
||||
_gedis._core._sets.erase(destination)
|
||||
for member in result_members:
|
||||
sadd(destination, member)
|
||||
return result_members.size()
|
||||
|
||||
# Computes the intersection of multiple sets and stores the result in a new set.
|
||||
# ---
|
||||
# @param destination: The key to store the resulting intersection set in.
|
||||
# @param keys: An array of set keys.
|
||||
# @return: The number of members in the resulting intersection set.
|
||||
func sinterstore(destination: String, keys: Array) -> int:
|
||||
var result_members = sinter(keys)
|
||||
_gedis._core._sets.erase(destination)
|
||||
for member in result_members:
|
||||
sadd(destination, member)
|
||||
return result_members.size()
|
||||
|
||||
# Computes the difference between multiple sets and stores the result in a new set.
|
||||
# ---
|
||||
# @param destination: The key to store the resulting difference set in.
|
||||
# @param keys: An array of set keys. The first key is the set to subtract from.
|
||||
# @return: The number of members in the resulting difference set.
|
||||
func sdiffstore(destination: String, keys: Array) -> int:
|
||||
var result_members = sdiff(keys)
|
||||
_gedis._core._sets.erase(destination)
|
||||
for member in result_members:
|
||||
sadd(destination, member)
|
||||
return result_members.size()
|
||||
|
||||
# Gets one or more random members from a set.
|
||||
# ---
|
||||
# @param key: The key of the set.
|
||||
# @param count: The number of random members to return. If positive, returns unique members. If negative, allows for repetitions.
|
||||
# @return: A single random member if count is 1, or an array of random members. Returns null or an empty array if the set is empty.
|
||||
func srandmember(key: String, count: int = 1):
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return null if count == 1 else []
|
||||
|
||||
var s: Dictionary = _gedis._core._sets.get(key, {})
|
||||
var members = s.keys()
|
||||
if members.is_empty():
|
||||
return null if count == 1 else []
|
||||
|
||||
if count == 1:
|
||||
return members.pick_random()
|
||||
|
||||
var result = []
|
||||
if count > 0:
|
||||
# Positive count: return unique elements
|
||||
if count >= members.size():
|
||||
members.shuffle()
|
||||
return members
|
||||
|
||||
var used_indices = {}
|
||||
while result.size() < count:
|
||||
var idx = randi() % members.size()
|
||||
if not used_indices.has(idx):
|
||||
result.append(members[idx])
|
||||
used_indices[idx] = true
|
||||
else: # count < 0
|
||||
# Negative count: repetitions are allowed
|
||||
var abs_count = abs(count)
|
||||
for _i in range(abs_count):
|
||||
result.append(members.pick_random())
|
||||
|
||||
return result
|
||||
@ -1 +0,0 @@
|
||||
uid://c2v7tr3s0wiv7
|
||||
@ -1,415 +0,0 @@
|
||||
# This class provides a sorted set data structure, where each member is associated with a score.
|
||||
# Members are stored in an array, sorted by score, allowing for efficient range queries. A dictionary
|
||||
# is used for quick lookups of member scores.
|
||||
#
|
||||
# Features:
|
||||
# - Add/update members with scores.
|
||||
# - Remove members.
|
||||
# - Retrieve members within a score range.
|
||||
# - Pop members that are "ready" based on a timestamp score.
|
||||
#
|
||||
# The implementation aims to be simple and efficient for common use cases in game development,
|
||||
# such as managing timed events, priority queues, or leaderboards.
|
||||
|
||||
extends RefCounted
|
||||
class_name GedisSortedSets
|
||||
|
||||
var _gedis: Gedis
|
||||
|
||||
func _init(gedis: Gedis):
|
||||
_gedis = gedis
|
||||
|
||||
# Adds a new member to the sorted set with the specified score.
|
||||
# If the member already exists, its score is updated, and its position in the set is adjusted.
|
||||
#
|
||||
# @param member: The member to add or update.
|
||||
# @param score: The score associated with the member.
|
||||
func add(key: String, member: String, score: int) -> int:
|
||||
_gedis._core._touch_type(key, _gedis._core._sorted_sets)
|
||||
var data: Dictionary = _gedis._core._sorted_sets.get(key, {
|
||||
"sorted_set": [],
|
||||
"member_scores": {}
|
||||
})
|
||||
|
||||
var new_member = not data.member_scores.has(member)
|
||||
if not new_member:
|
||||
remove(key, member)
|
||||
data = _gedis._core._sorted_sets.get(key, {
|
||||
"sorted_set": [],
|
||||
"member_scores": {}
|
||||
})
|
||||
|
||||
data.member_scores[member] = score
|
||||
var entry = [score, member]
|
||||
var inserted: bool = false
|
||||
for i in range(data.sorted_set.size()):
|
||||
if score < data.sorted_set[i][0]:
|
||||
data.sorted_set.insert(i, entry)
|
||||
inserted = true
|
||||
break
|
||||
if not inserted:
|
||||
data.sorted_set.append(entry)
|
||||
|
||||
_gedis._core._sorted_sets[key] = data
|
||||
if new_member:
|
||||
_gedis.publish("gedis:keyspace:" + key, "set")
|
||||
return 1 if new_member else 0
|
||||
|
||||
|
||||
# Removes a member from the sorted set.
|
||||
#
|
||||
# @param member: The member to remove.
|
||||
func remove(key: String, member: String) -> int:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return 0
|
||||
if not _gedis._core._sorted_sets.has(key):
|
||||
return 0
|
||||
|
||||
var data: Dictionary = _gedis._core._sorted_sets[key]
|
||||
if not data.member_scores.has(member):
|
||||
return 0
|
||||
|
||||
var score = data.member_scores[member]
|
||||
data.member_scores.erase(member)
|
||||
|
||||
for i in range(data.sorted_set.size()):
|
||||
if data.sorted_set[i][0] == score and data.sorted_set[i][1] == member:
|
||||
data.sorted_set.remove_at(i)
|
||||
if data.sorted_set.is_empty():
|
||||
_gedis._core._sorted_sets.erase(key)
|
||||
_gedis.publish("gedis:keyspace:" + key, "del")
|
||||
else:
|
||||
_gedis._core._sorted_sets[key] = data
|
||||
return 1
|
||||
return 0
|
||||
|
||||
func zexists(key: String) -> bool:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return false
|
||||
return _gedis._core._sorted_sets.has(key)
|
||||
|
||||
func zcard(key: String) -> int:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return 0
|
||||
if not _gedis._core._sorted_sets.has(key):
|
||||
return 0
|
||||
return _gedis._core._sorted_sets[key].sorted_set.size()
|
||||
|
||||
# Returns a range of members from the sorted set, ordered by score.
|
||||
#
|
||||
# @param start: The starting index of the range.
|
||||
# @param stop: The ending index of the range.
|
||||
# @param withscores: Whether to return scores along with members.
|
||||
# @return: An array of members, or an array of [member, score] pairs if withscores is true.
|
||||
func zrange(key: String, start, stop, withscores: bool = false) -> Array:
|
||||
return _zrange(key, start, stop, withscores, false)
|
||||
|
||||
# Returns a range of members from the sorted set, ordered by score in reverse.
|
||||
#
|
||||
# @param start: The starting index of the range.
|
||||
# @param stop: The ending index of the range.
|
||||
# @param withscores: Whether to return scores along with members.
|
||||
# @return: An array of members, or an array of [member, score] pairs if withscores is true.
|
||||
func zrevrange(key: String, start, stop, withscores: bool = false) -> Array:
|
||||
return _zrange(key, start, stop, withscores, true)
|
||||
|
||||
func _zrange(key: String, start, stop, withscores: bool, reverse: bool) -> Array:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return []
|
||||
if not _gedis._core._sorted_sets.has(key):
|
||||
return []
|
||||
|
||||
var data: Dictionary = _gedis._core._sorted_sets[key]
|
||||
var sorted_set: Array = data.sorted_set
|
||||
if reverse:
|
||||
sorted_set = sorted_set.duplicate()
|
||||
sorted_set.reverse()
|
||||
|
||||
var set_len: int = sorted_set.size()
|
||||
|
||||
var start_index = start
|
||||
if start_index < 0:
|
||||
start_index = set_len + start_index
|
||||
|
||||
var stop_index = stop
|
||||
if stop_index < 0:
|
||||
stop_index = set_len + stop_index
|
||||
|
||||
if start_index < 0:
|
||||
start_index = 0
|
||||
|
||||
if stop_index >= set_len:
|
||||
stop_index = set_len - 1
|
||||
|
||||
if start_index > stop_index or start_index >= set_len:
|
||||
return []
|
||||
|
||||
var result: Array = []
|
||||
for i in range(start_index, stop_index + 1):
|
||||
var entry = sorted_set[i]
|
||||
var score = entry[0]
|
||||
var member = entry[1]
|
||||
if withscores:
|
||||
result.append([member, score])
|
||||
else:
|
||||
result.append(member)
|
||||
return result
|
||||
|
||||
# Returns a range of members from the sorted set, ordered by score.
|
||||
#
|
||||
# @param min_score: The minimum score of the range.
|
||||
# @param max_score: The maximum score of the range.
|
||||
# @param withscores: Whether to return scores along with members.
|
||||
# @return: An array of members, or an array of [member, score] pairs if withscores is true.
|
||||
# Returns a range of members from the sorted set, ordered by score.
|
||||
func zrangebyscore(key: String, min_score, max_score, withscores: bool = false) -> Array:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return []
|
||||
if not _gedis._core._sorted_sets.has(key):
|
||||
return []
|
||||
|
||||
var data: Dictionary = _gedis._core._sorted_sets[key]
|
||||
var result: Array = []
|
||||
for entry in data.sorted_set:
|
||||
var score = entry[0]
|
||||
var member = entry[1]
|
||||
if score >= min_score and score <= max_score:
|
||||
if withscores:
|
||||
result.append([member, score])
|
||||
else:
|
||||
result.append(member)
|
||||
return result
|
||||
|
||||
# Returns a range of members from the sorted set, ordered by score in reverse.
|
||||
#
|
||||
# @param min_score: The minimum score of the range.
|
||||
# @param max_score: The maximum score of the range.
|
||||
# @param withscores: Whether to return scores along with members.
|
||||
# @return: An array of members, or an array of [member, score] pairs if withscores is true.
|
||||
# Returns a range of members from the sorted set, ordered by score in reverse.
|
||||
func zrevrangebyscore(key: String, min_score, max_score, withscores: bool = false) -> Array:
|
||||
var result = zrangebyscore(key, min_score, max_score, withscores)
|
||||
result.reverse()
|
||||
return result
|
||||
|
||||
# Removes and returns members with scores up to the given 'now' value.
|
||||
# This is useful for processing items that are due, like in a task scheduler.
|
||||
#
|
||||
# @param now: The current time or score to check against.
|
||||
# @return: An array of members that were popped.
|
||||
func pop_ready(key: String, now: int) -> Array:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return []
|
||||
if not _gedis._core._sorted_sets.has(key):
|
||||
return []
|
||||
|
||||
var data: Dictionary = _gedis._core._sorted_sets[key]
|
||||
var ready_members: Array = []
|
||||
var i: int = 0
|
||||
while i < data.sorted_set.size():
|
||||
var entry = data.sorted_set[i]
|
||||
if entry[0] <= now:
|
||||
ready_members.append(entry[1])
|
||||
data.member_scores.erase(entry[1])
|
||||
data.sorted_set.remove_at(i)
|
||||
else:
|
||||
break
|
||||
|
||||
if data.sorted_set.is_empty():
|
||||
_gedis._core._sorted_sets.erase(key)
|
||||
_gedis.publish("gedis:keyspace:" + key, "del")
|
||||
else:
|
||||
_gedis._core._sorted_sets[key] = data
|
||||
return ready_members
|
||||
|
||||
|
||||
# Returns the score of a member in the sorted set.
|
||||
# ---
|
||||
# @param key: The key of the sorted set.
|
||||
# @param member: The member to get the score of.
|
||||
# @return: The score of the member, or null if the member does not exist.
|
||||
func zscore(key: String, member: String) -> Variant:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return null
|
||||
if not _gedis._core._sorted_sets.has(key):
|
||||
return null
|
||||
|
||||
var data: Dictionary = _gedis._core._sorted_sets[key]
|
||||
if not data.member_scores.has(member):
|
||||
return null
|
||||
|
||||
return data.member_scores[member]
|
||||
|
||||
|
||||
# Returns the rank of a member in the sorted set, with scores ordered from low to high.
|
||||
# The rank is 0-based.
|
||||
# ---
|
||||
# @param key: The key of the sorted set.
|
||||
# @param member: The member to get the rank of.
|
||||
# @return: The rank of the member, or null if the member does not exist.
|
||||
func zrank(key: String, member: String) -> Variant:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return null
|
||||
if not _gedis._core._sorted_sets.has(key):
|
||||
return null
|
||||
|
||||
var data: Dictionary = _gedis._core._sorted_sets[key]
|
||||
if not data.member_scores.has(member):
|
||||
return null
|
||||
|
||||
for i in range(data.sorted_set.size()):
|
||||
if data.sorted_set[i][1] == member:
|
||||
return i
|
||||
return null
|
||||
|
||||
|
||||
# Returns the rank of a member in the sorted set, with scores ordered from high to low.
|
||||
# The rank is 0-based.
|
||||
# ---
|
||||
# @param key: The key of the sorted set.
|
||||
# @param member: The member to get the rank of.
|
||||
# @return: The rank of the member, or null if the member does not exist.
|
||||
func zrevrank(key: String, member: String) -> Variant:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return null
|
||||
if not _gedis._core._sorted_sets.has(key):
|
||||
return null
|
||||
|
||||
var data: Dictionary = _gedis._core._sorted_sets[key]
|
||||
if not data.member_scores.has(member):
|
||||
return null
|
||||
|
||||
for i in range(data.sorted_set.size() - 1, -1, -1):
|
||||
if data.sorted_set[i][1] == member:
|
||||
return data.sorted_set.size() - 1 - i
|
||||
return null
|
||||
|
||||
|
||||
# Returns the number of members in a sorted set with scores within the given range.
|
||||
# ---
|
||||
# @param key: The key of the sorted set.
|
||||
# @param min_score: The minimum score of the range.
|
||||
# @param max_score: The maximum score of the range.
|
||||
# @return: The number of members in the specified score range.
|
||||
func zcount(key: String, min_score, max_score) -> int:
|
||||
if _gedis._expiry._is_expired(key):
|
||||
return 0
|
||||
if not _gedis._core._sorted_sets.has(key):
|
||||
return 0
|
||||
|
||||
var data: Dictionary = _gedis._core._sorted_sets[key]
|
||||
var count: int = 0
|
||||
for entry in data.sorted_set:
|
||||
var score = entry[0]
|
||||
if score >= min_score and score <= max_score:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
# Increments the score of a member in a sorted set.
|
||||
# If the member does not exist, it is added with the increment as its score.
|
||||
# ---
|
||||
# @param key: The key of the sorted set.
|
||||
# @param increment: The amount to increment the score by.
|
||||
# @param member: The member whose score to increment.
|
||||
# @return: The new score of the member.
|
||||
func zincrby(key: String, increment, member: String) -> Variant:
|
||||
_gedis._core._touch_type(key, _gedis._core._sorted_sets)
|
||||
var current_score = zscore(key, member)
|
||||
if current_score == null:
|
||||
current_score = 0
|
||||
var new_score = current_score + increment
|
||||
add(key, member, new_score)
|
||||
return new_score
|
||||
|
||||
# Computes the union of multiple sorted sets and stores the result in a new sorted set.
|
||||
# ---
|
||||
# @param destination: The key to store the resulting sorted set in.
|
||||
# @param keys: An array of sorted set keys.
|
||||
# @param aggregate: The aggregation strategy for scores of the same member ("SUM", "MIN", "MAX").
|
||||
# @return: The number of members in the resulting sorted set.
|
||||
func zunionstore(destination: String, keys: Array, aggregate: String = "SUM") -> int:
|
||||
var temp_scores: Dictionary = {}
|
||||
for key in keys:
|
||||
if not _gedis._core._sorted_sets.has(key):
|
||||
continue
|
||||
var data: Dictionary = _gedis._core._sorted_sets[key]
|
||||
for member in data.member_scores:
|
||||
var score = data.member_scores[member]
|
||||
if not temp_scores.has(member):
|
||||
temp_scores[member] = score
|
||||
else:
|
||||
match aggregate.to_upper():
|
||||
"SUM":
|
||||
temp_scores[member] += score
|
||||
"MIN":
|
||||
temp_scores[member] = min(temp_scores[member], score)
|
||||
"MAX":
|
||||
temp_scores[member] = max(temp_scores[member], score)
|
||||
|
||||
if _gedis._core._sorted_sets.has(destination):
|
||||
_gedis.del([destination])
|
||||
|
||||
for member in temp_scores:
|
||||
add(destination, member, temp_scores[member])
|
||||
|
||||
if not _gedis._core._sorted_sets.has(destination):
|
||||
return 0
|
||||
return _gedis._core._sorted_sets[destination].sorted_set.size()
|
||||
|
||||
|
||||
# Computes the intersection of multiple sorted sets and stores the result in a new sorted set.
|
||||
# ---
|
||||
# @param destination: The key to store the resulting sorted set in.
|
||||
# @param keys: An array of sorted set keys.
|
||||
# @param aggregate: The aggregation strategy for scores of the same member ("SUM", "MIN", "MAX").
|
||||
# @return: The number of members in the resulting sorted set.
|
||||
func zinterstore(destination: String, keys: Array, aggregate: String = "SUM") -> int:
|
||||
if keys.is_empty():
|
||||
return 0
|
||||
|
||||
var member_sets: Array = []
|
||||
for key in keys:
|
||||
if not _gedis._core._sorted_sets.has(key):
|
||||
return 0
|
||||
var data: Dictionary = _gedis._core._sorted_sets[key]
|
||||
member_sets.append(data.member_scores.keys())
|
||||
|
||||
var intersection = member_sets[0]
|
||||
for i in range(1, member_sets.size()):
|
||||
var next_set = member_sets[i]
|
||||
var current_intersection: Array = []
|
||||
for member in intersection:
|
||||
if member in next_set:
|
||||
current_intersection.append(member)
|
||||
intersection = current_intersection
|
||||
|
||||
var temp_scores: Dictionary = {}
|
||||
for member in intersection:
|
||||
var score_sum = 0
|
||||
var score_min = INF
|
||||
var score_max = - INF
|
||||
for key in keys:
|
||||
var data: Dictionary = _gedis._core._sorted_sets[key]
|
||||
var score = data.member_scores[member]
|
||||
score_sum += score
|
||||
score_min = min(score_min, score)
|
||||
score_max = max(score_max, score)
|
||||
|
||||
match aggregate.to_upper():
|
||||
"SUM":
|
||||
temp_scores[member] = score_sum
|
||||
"MIN":
|
||||
temp_scores[member] = score_min
|
||||
"MAX":
|
||||
temp_scores[member] = score_max
|
||||
|
||||
if _gedis._core._sorted_sets.has(destination):
|
||||
_gedis.del([destination])
|
||||
|
||||
for member in temp_scores:
|
||||
add(destination, member, temp_scores[member])
|
||||
|
||||
if not _gedis._core._sorted_sets.has(destination):
|
||||
return 0
|
||||
return _gedis._core._sorted_sets[destination].sorted_set.size()
|
||||
@ -1 +0,0 @@
|
||||
uid://nld5hwedd25b
|
||||
@ -1,139 +0,0 @@
|
||||
extends RefCounted
|
||||
class_name GedisStrings
|
||||
|
||||
var _gedis: Gedis
|
||||
|
||||
func _init(gedis: Gedis):
|
||||
_gedis = gedis
|
||||
|
||||
# -----------------
|
||||
# String/number API
|
||||
# -----------------
|
||||
func set_value(key: StringName, value: Variant) -> void:
|
||||
_gedis._core._touch_type(str(key), _gedis._core._store)
|
||||
_gedis._core._store[str(key)] = value
|
||||
_gedis.publish("gedis:keyspace:" + str(key), "set")
|
||||
|
||||
func get_value(key: StringName, default_value: Variant = null) -> Variant:
|
||||
if _gedis._expiry._is_expired(str(key)):
|
||||
return default_value
|
||||
return _gedis._core._store.get(str(key), default_value)
|
||||
|
||||
# del: accept String or Array of keys
|
||||
func del(keys) -> int:
|
||||
if typeof(keys) == TYPE_ARRAY:
|
||||
var count = 0
|
||||
for k in keys:
|
||||
if _gedis._expiry._is_expired(str(k)):
|
||||
continue
|
||||
if exists(str(k)):
|
||||
_gedis.publish("gedis:keyspace:" + str(k), "del")
|
||||
_gedis._core._delete_all_types_for_key(str(k))
|
||||
count += 1
|
||||
return count
|
||||
else:
|
||||
var k = str(keys)
|
||||
var existed := int(exists(k))
|
||||
if existed > 0:
|
||||
_gedis.publish("gedis:keyspace:" + k, "del")
|
||||
_gedis._core._delete_all_types_for_key(k)
|
||||
return existed
|
||||
|
||||
# exists: if Array -> return number of existing keys, else boolean for single key
|
||||
func exists(keys) -> Variant:
|
||||
if typeof(keys) == TYPE_ARRAY:
|
||||
var cnt = 0
|
||||
for k in keys:
|
||||
if not _gedis._expiry._is_expired(str(k)) and _gedis._core.key_exists(str(k)):
|
||||
cnt += 1
|
||||
return cnt
|
||||
else:
|
||||
var k = str(keys)
|
||||
if _gedis._expiry._is_expired(k):
|
||||
return false
|
||||
return _gedis._core.key_exists(k)
|
||||
|
||||
# key_exists: explicit single-key boolean (keeps parity with C++ API)
|
||||
func key_exists(key: String) -> bool:
|
||||
return bool(exists(key))
|
||||
|
||||
func incrby(key: String, amount: int = 1) -> int:
|
||||
var k := str(key)
|
||||
var current: int = 0
|
||||
if _gedis._expiry._is_expired(k):
|
||||
current = 0
|
||||
else:
|
||||
var raw = get_value(k, 0)
|
||||
match typeof(raw):
|
||||
TYPE_NIL:
|
||||
current = 0
|
||||
TYPE_INT:
|
||||
current = int(raw)
|
||||
TYPE_FLOAT:
|
||||
current = int(raw)
|
||||
TYPE_STRING:
|
||||
var s := str(raw).strip_edges()
|
||||
if s.find(".") != -1:
|
||||
current = int(float(s))
|
||||
else:
|
||||
# int(s) will raise on invalid strings; rely on Godot to convert or raise as needed.
|
||||
current = int(s)
|
||||
_:
|
||||
current = int(raw)
|
||||
var v: int = current + int(amount)
|
||||
# Store as an integer to keep types consistent
|
||||
_gedis._core._touch_type(k, _gedis._core._store)
|
||||
_gedis._core._store[k] = v
|
||||
_gedis.publish("gedis:keyspace:" + k, "set")
|
||||
return v
|
||||
|
||||
func decrby(key: String, amount: int = 1) -> int:
|
||||
return incrby(key, -int(amount))
|
||||
|
||||
func keys(pattern: String = "*") -> Array:
|
||||
var all: Dictionary = _gedis._core._get_all_keys()
|
||||
var rx := _gedis._utils._glob_to_regex(pattern)
|
||||
var out: Array = []
|
||||
for k in all.keys():
|
||||
if not _gedis._expiry._is_expired(str(k)) and rx.search(str(k)) != null:
|
||||
out.append(str(k))
|
||||
return out
|
||||
|
||||
func mset(dict: Dictionary) -> void:
|
||||
for k in dict.keys():
|
||||
set_value(str(k), dict[k])
|
||||
|
||||
func mget(keys: Array) -> Array:
|
||||
var out: Array = []
|
||||
for k in keys:
|
||||
out.append(get_value(str(k), null))
|
||||
return out
|
||||
|
||||
func append(key: String, value: String) -> int:
|
||||
var k := str(key)
|
||||
var current_value := get_value(k, "")
|
||||
if typeof(current_value) != TYPE_STRING:
|
||||
current_value = str(current_value)
|
||||
var new_value: String = current_value + value
|
||||
set_value(k, new_value)
|
||||
return new_value.length()
|
||||
|
||||
func getset(key: String, value: Variant) -> Variant:
|
||||
var k := str(key)
|
||||
var old_value = get_value(k)
|
||||
set_value(k, value)
|
||||
return old_value
|
||||
|
||||
func strlen(key: String) -> int:
|
||||
var k := str(key)
|
||||
var value = get_value(k)
|
||||
if typeof(value) == TYPE_STRING:
|
||||
return value.length()
|
||||
return 0
|
||||
|
||||
func setnx(key: String, value: Variant) -> int:
|
||||
var k := str(key)
|
||||
if not _gedis._expiry._is_expired(k) and key_exists(k):
|
||||
return 0
|
||||
set_value(k, value)
|
||||
return 1
|
||||
@ -1 +0,0 @@
|
||||
uid://dv4qlsimic35p
|
||||
@ -1,43 +0,0 @@
|
||||
class_name GedisUtils
|
||||
var _regex_cache := {}
|
||||
|
||||
# ----------------
|
||||
# Utility functions
|
||||
# ----------------
|
||||
func _glob_to_regex(glob: String) -> RegEx:
|
||||
if _regex_cache.has(glob):
|
||||
return _regex_cache[glob]
|
||||
|
||||
var escaped := ""
|
||||
for ch in glob:
|
||||
match ch:
|
||||
".":
|
||||
escaped += "\\."
|
||||
"*":
|
||||
escaped += ".*"
|
||||
"?":
|
||||
escaped += "."
|
||||
"+":
|
||||
escaped += "\\+"
|
||||
"(":
|
||||
escaped += "\\("
|
||||
")":
|
||||
escaped += "\\)"
|
||||
"[":
|
||||
escaped += "\\["
|
||||
"]":
|
||||
escaped += "\\]"
|
||||
"^":
|
||||
escaped += "\\^"
|
||||
"$":
|
||||
escaped += "\\$"
|
||||
"|":
|
||||
escaped += "\\|"
|
||||
"\\":
|
||||
escaped += "\\\\"
|
||||
_:
|
||||
escaped += ch
|
||||
var r := RegEx.new()
|
||||
r.compile("^%s$" % escaped)
|
||||
_regex_cache[glob] = r
|
||||
return r
|
||||
@ -1 +0,0 @@
|
||||
uid://b3eglfuyrjdgp
|
||||
@ -1,107 +0,0 @@
|
||||
class_name GedisJSONSnapshotBackend extends GedisPersistenceBackend
|
||||
|
||||
func save(data: Dictionary, options: Dictionary) -> int:
|
||||
var path: String = options.get("path", "")
|
||||
if path.is_empty():
|
||||
push_error("JSON snapshot backend requires a 'path' in options.")
|
||||
return FAILED
|
||||
|
||||
var file = FileAccess.open(path, FileAccess.WRITE)
|
||||
if not file:
|
||||
push_error("Failed to open file for writing: %s" % path)
|
||||
return FAILED
|
||||
|
||||
# Serialize variants to strings
|
||||
var serializable_data = {}
|
||||
for bucket_name in data:
|
||||
var bucket = data[bucket_name]
|
||||
var serializable_bucket = {}
|
||||
for key in bucket:
|
||||
var value = bucket[key]
|
||||
if typeof(value) in [TYPE_OBJECT, TYPE_RID, TYPE_CALLABLE, TYPE_SIGNAL]:
|
||||
serializable_bucket[key] = var_to_str(value)
|
||||
else:
|
||||
serializable_bucket[key] = value
|
||||
serializable_data[bucket_name] = serializable_bucket
|
||||
|
||||
var json_string = JSON.stringify(serializable_data, "\t")
|
||||
file.store_string(json_string)
|
||||
file.close()
|
||||
|
||||
return OK
|
||||
|
||||
|
||||
func load(options: Dictionary) -> Dictionary:
|
||||
var path: String = options.get("path", "")
|
||||
if path.is_empty():
|
||||
push_error("JSON snapshot backend requires a 'path' in options.")
|
||||
return {}
|
||||
|
||||
if not FileAccess.file_exists(path):
|
||||
return {} # Return empty dict if file doesn't exist, not an error
|
||||
|
||||
var file = FileAccess.open(path, FileAccess.READ)
|
||||
if not file:
|
||||
push_error("Failed to open file for reading: %s" % path)
|
||||
return {}
|
||||
|
||||
var json_string: String = file.get_as_text()
|
||||
file.close()
|
||||
|
||||
var json = JSON.new()
|
||||
var error = json.parse(json_string)
|
||||
if error != OK:
|
||||
push_error("Failed to parse JSON from file: %s" % path)
|
||||
return {}
|
||||
|
||||
var loaded_data: Dictionary = json.get_data()
|
||||
|
||||
# Deserialize variants from strings
|
||||
for bucket_name in loaded_data:
|
||||
var bucket = loaded_data[bucket_name]
|
||||
if typeof(bucket) == TYPE_DICTIONARY:
|
||||
for key in bucket:
|
||||
var value = bucket[key]
|
||||
if typeof(value) == TYPE_STRING and value.begins_with("@"):
|
||||
bucket[key] = str_to_var(value)
|
||||
elif typeof(bucket) == TYPE_STRING and bucket.begins_with("@"):
|
||||
loaded_data[bucket_name] = str_to_var(bucket)
|
||||
|
||||
return loaded_data
|
||||
|
||||
func serialize(data: Dictionary) -> String:
|
||||
var serializable_data = {}
|
||||
for bucket_name in data:
|
||||
var bucket = data[bucket_name]
|
||||
var serializable_bucket = {}
|
||||
for key in bucket:
|
||||
var value = bucket[key]
|
||||
if typeof(value) in [TYPE_OBJECT, TYPE_RID, TYPE_CALLABLE, TYPE_SIGNAL]:
|
||||
serializable_bucket[key] = var_to_str(value)
|
||||
else:
|
||||
serializable_bucket[key] = value
|
||||
serializable_data[bucket_name] = serializable_bucket
|
||||
|
||||
return JSON.stringify(serializable_data, "\t")
|
||||
|
||||
func deserialize(data: String) -> Dictionary:
|
||||
var json = JSON.new()
|
||||
var error = json.parse(data)
|
||||
if error != OK:
|
||||
push_error("Failed to parse JSON from string")
|
||||
return {}
|
||||
|
||||
var loaded_data: Dictionary = json.get_data()
|
||||
|
||||
# Deserialize variants from strings
|
||||
for bucket_name in loaded_data:
|
||||
var bucket = loaded_data[bucket_name]
|
||||
if typeof(bucket) == TYPE_DICTIONARY:
|
||||
for key in bucket:
|
||||
var value = bucket[key]
|
||||
if typeof(value) == TYPE_STRING and value.begins_with("@"):
|
||||
bucket[key] = str_to_var(value)
|
||||
elif typeof(bucket) == TYPE_STRING and bucket.begins_with("@"):
|
||||
loaded_data[bucket_name] = str_to_var(bucket)
|
||||
|
||||
return loaded_data
|
||||
@ -1 +0,0 @@
|
||||
uid://dw5ib42q321e4
|
||||
@ -1,29 +0,0 @@
|
||||
class_name GedisPersistenceBackend
|
||||
|
||||
# Base class for persistence backends.
|
||||
# All backends should inherit from this class and implement the `save` and `load` methods.
|
||||
|
||||
func save(data: Dictionary, options: Dictionary) -> int:
|
||||
# This method should be implemented by the child class.
|
||||
# It should save the data to the persistence layer and return OK or FAILED.
|
||||
push_error("save() not implemented in the persistence backend.")
|
||||
return FAILED
|
||||
|
||||
|
||||
func load(options: Dictionary) -> Dictionary:
|
||||
# This method should be implemented by the child class.
|
||||
# It should load the data from the persistence layer and return it as a Dictionary.
|
||||
push_error("load() not implemented in the persistence backend.")
|
||||
return {}
|
||||
|
||||
func serialize(data: Dictionary) -> String:
|
||||
# This method should be implemented by the child class.
|
||||
# It should serialize the data to a string.
|
||||
push_error("serialize() not implemented in the persistence backend.")
|
||||
return ""
|
||||
|
||||
func deserialize(data: String) -> Dictionary:
|
||||
# This method should be implemented by the child class.
|
||||
# It should deserialize the data from a string.
|
||||
push_error("deserialize() not implemented in the persistence backend.")
|
||||
return {}
|
||||
@ -1 +0,0 @@
|
||||
uid://cqoc0ht0swvnp
|
||||
@ -1,15 +0,0 @@
|
||||
class_name GedisProcessDeltaTimeSource
|
||||
extends GedisTimeSource
|
||||
|
||||
var current_time: int = 0
|
||||
|
||||
func _init() -> void:
|
||||
current_time = Time.get_unix_time_from_system() * 1000
|
||||
|
||||
func tick(delta: float) -> void:
|
||||
current_time += int(delta * 1000)
|
||||
instance.purge_expired()
|
||||
|
||||
# Returns the current time as a Unix timestamp with milliseconds.
|
||||
func get_time() -> int:
|
||||
return current_time
|
||||
@ -1 +0,0 @@
|
||||
uid://cepu7wbyednk
|
||||
@ -1,6 +0,0 @@
|
||||
class_name GedisTickTimeSource
|
||||
extends GedisTimeSource
|
||||
|
||||
# Returns the current time as a Unix timestamp with milliseconds.
|
||||
func get_time() -> int:
|
||||
return Time.get_ticks_msec()
|
||||
@ -1 +0,0 @@
|
||||
uid://dyaygbjbae3jp
|
||||
@ -1,12 +0,0 @@
|
||||
class_name GedisTimeSource
|
||||
extends RefCounted
|
||||
|
||||
var instance: Gedis
|
||||
|
||||
# Returns the current time as a Unix timestamp in milliseconds.
|
||||
func get_time() -> int:
|
||||
return 0
|
||||
|
||||
# Increments the time [Optional]
|
||||
func tick(_delta) -> void:
|
||||
instance.purge_expired()
|
||||
@ -1 +0,0 @@
|
||||
uid://b36i3x1dhi4b6
|
||||
@ -1,6 +0,0 @@
|
||||
class_name GedisUnixTimeSource
|
||||
extends GedisTimeSource
|
||||
|
||||
# Returns the current time as a Unix timestamp with milliseconds.
|
||||
func get_time() -> int:
|
||||
return Time.get_unix_time_from_system() * 1000
|
||||
@ -1 +0,0 @@
|
||||
uid://bsxup2elcrrr7
|
||||
@ -1,22 +0,0 @@
|
||||
@tool
|
||||
extends VBoxContainer
|
||||
|
||||
@export var pub_sub_tree: Tree
|
||||
@export var fetch_pubsub_button: Button
|
||||
@export var instance_selector: OptionButton
|
||||
|
||||
var plugin
|
||||
|
||||
func _ready() -> void:
|
||||
_populate_pubsub_tree()
|
||||
fetch_pubsub_button.pressed.connect(_on_fetch_pubsub_pressed)
|
||||
|
||||
func _on_fetch_pubsub_pressed():
|
||||
if plugin:
|
||||
var selected_id = instance_selector.get_item_id(instance_selector.selected)
|
||||
plugin.send_message_to_game("request_instance_data", [selected_id, "pubsub"])
|
||||
|
||||
func _populate_pubsub_tree():
|
||||
if not pub_sub_tree:
|
||||
return
|
||||
pub_sub_tree.clear()
|
||||
@ -1 +0,0 @@
|
||||
uid://dbrrt18c60tyc
|
||||
@ -1,102 +0,0 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://c6w7w7w7w7w7"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dbrrt18c60tyc" path="res://addons/Gedis/debugger/gedis_debugger_panel.gd" id="1_z8w8w"]
|
||||
|
||||
[sub_resource type="Theme" id="Theme_1"]
|
||||
Label/colors/font_color = Color(1, 0.647059, 0, 1)
|
||||
|
||||
[node name="Dashboard" type="VBoxContainer" node_paths=PackedStringArray("pub_sub_tree", "fetch_pubsub_button", "instance_selector")]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_right = -1152.0
|
||||
offset_bottom = -648.0
|
||||
script = ExtResource("1_z8w8w")
|
||||
pub_sub_tree = NodePath("TabContainer/PubSub/HSplitContainer/PubSubTree")
|
||||
fetch_pubsub_button = NodePath("TopPanel/FetchPubSubButton")
|
||||
instance_selector = NodePath("TopPanel/instance_selector")
|
||||
|
||||
[node name="status_label" type="Label" parent="."]
|
||||
layout_mode = 2
|
||||
theme = SubResource("Theme_1")
|
||||
text = "Waiting for game connection..."
|
||||
|
||||
[node name="TopPanel" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="instance_selector" type="OptionButton" parent="TopPanel"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="RefreshButton" type="Button" parent="TopPanel"]
|
||||
layout_mode = 2
|
||||
text = "Refresh Instances"
|
||||
|
||||
[node name="FetchKeysButton" type="Button" parent="TopPanel"]
|
||||
layout_mode = 2
|
||||
text = "Fetch Keys"
|
||||
|
||||
[node name="FetchPubSubButton" type="Button" parent="TopPanel"]
|
||||
layout_mode = 2
|
||||
text = "Fetch Pub/Sub"
|
||||
|
||||
[node name="SearchPanel" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="search_box" type="LineEdit" parent="SearchPanel"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Filter keys..."
|
||||
|
||||
[node name="filter_button" type="Button" parent="SearchPanel"]
|
||||
layout_mode = 2
|
||||
text = "Filter"
|
||||
|
||||
[node name="TabContainer" type="TabContainer" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
current_tab = 0
|
||||
|
||||
[node name="Keys" type="VBoxContainer" parent="TabContainer"]
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 0
|
||||
|
||||
[node name="HSplitContainer" type="HSplitContainer" parent="TabContainer/Keys"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="key_list" type="Tree" parent="TabContainer/Keys/HSplitContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
columns = 3
|
||||
column_titles_visible = true
|
||||
|
||||
[node name="key_value_view" type="TextEdit" parent="TabContainer/Keys/HSplitContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
editable = false
|
||||
|
||||
[node name="PubSub" type="VBoxContainer" parent="TabContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 1
|
||||
|
||||
[node name="HSplitContainer" type="HSplitContainer" parent="TabContainer/PubSub"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="PubSubTree" type="Tree" parent="TabContainer/PubSub/HSplitContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
hide_root = true
|
||||
|
||||
[node name="PubSubEventsTree" type="Tree" parent="TabContainer/PubSub/HSplitContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
columns = 2
|
||||
column_titles_visible = true
|
||||
hide_root = true
|
||||
@ -1,567 +0,0 @@
|
||||
class_name Gedis extends Node
|
||||
|
||||
# Component instances
|
||||
var _core: GedisCore
|
||||
var _expiry: GedisExpiry
|
||||
var _time_source: GedisTimeSource
|
||||
var _strings: GedisStrings
|
||||
var _hashes: GedisHashes
|
||||
var _lists: GedisLists
|
||||
var _sets: GedisSets
|
||||
var _sorted_sets: GedisSortedSets
|
||||
var _pubsub: GedisPubSub
|
||||
var _debugger_component: GedisDebugger
|
||||
var _utils: GedisUtils
|
||||
var _persistence_backends: Dictionary = {}
|
||||
var _default_persistence_backend: String = ""
|
||||
|
||||
# Instance registry
|
||||
static var _instances: Array = []
|
||||
static var _next_instance_id: int = 0
|
||||
var _instance_id: int = -1
|
||||
var _instance_name: String = ""
|
||||
static var _debugger_registered = false
|
||||
|
||||
func _init() -> void:
|
||||
# assign id and register
|
||||
_instance_id = _next_instance_id
|
||||
_next_instance_id += 1
|
||||
_instance_name = "Gedis_%d" % _instance_id
|
||||
_instances.append(self)
|
||||
|
||||
# Instantiate components
|
||||
_core = GedisCore.new(self)
|
||||
_utils = GedisUtils.new()
|
||||
_expiry = GedisExpiry.new(self)
|
||||
_strings = GedisStrings.new(self)
|
||||
_hashes = GedisHashes.new(self)
|
||||
_lists = GedisLists.new(self)
|
||||
_sets = GedisSets.new(self)
|
||||
_sorted_sets = GedisSortedSets.new(self)
|
||||
_pubsub = GedisPubSub.new(self)
|
||||
_debugger_component = GedisDebugger.new(self)
|
||||
|
||||
_time_source = GedisUnixTimeSource.new()
|
||||
_time_source.instance = self
|
||||
|
||||
_pubsub.pubsub_message.connect(_on_pubsub_message)
|
||||
_pubsub.psub_message.connect(_on_psub_message)
|
||||
|
||||
GedisDebugger._ensure_debugger_is_registered()
|
||||
|
||||
func _on_pubsub_message(channel: String, message: Variant) -> void:
|
||||
pubsub_message.emit(channel, message)
|
||||
|
||||
func _on_psub_message(pattern: String, channel: String, message: Variant) -> void:
|
||||
psub_message.emit(pattern, channel, message)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
# unregister instance
|
||||
for i in range(_instances.size()):
|
||||
if _instances[i] == self:
|
||||
_instances.remove_at(i)
|
||||
break
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_time_source.tick(delta)
|
||||
|
||||
# --- Time Source ---
|
||||
func set_time_source(p_time_source: GedisTimeSource) -> void:
|
||||
p_time_source.instance = self
|
||||
_time_source = p_time_source
|
||||
|
||||
func get_time_source() -> GedisTimeSource:
|
||||
return _time_source
|
||||
|
||||
# --- Public API ---
|
||||
|
||||
signal pubsub_message(channel, message)
|
||||
signal psub_message(pattern, channel, message)
|
||||
|
||||
## Sets a value for a key
|
||||
func set_value(key: StringName, value: Variant) -> void:
|
||||
_strings.set_value(key, value)
|
||||
|
||||
## Sets a key to a value with an expiration time in seconds.
|
||||
func setex(key: StringName, seconds: int, value: Variant) -> void:
|
||||
set_value(key, value)
|
||||
expire(key, seconds)
|
||||
|
||||
## Gets the string value of a key.
|
||||
func get_value(key: StringName, default_value: Variant = null) -> Variant:
|
||||
return _strings.get_value(key, default_value)
|
||||
|
||||
## Deletes one or more keys.
|
||||
func del(keys) -> int:
|
||||
return _strings.del(keys)
|
||||
|
||||
## Checks if one or more keys exist.
|
||||
func exists(keys) -> Variant:
|
||||
return _strings.exists(keys)
|
||||
|
||||
## Checks if a key exists.
|
||||
func key_exists(key: String) -> bool:
|
||||
return _strings.key_exists(key)
|
||||
|
||||
## Increments the integer value of a key by a given amount.
|
||||
func incrby(key: String, amount: int = 1) -> int:
|
||||
return _strings.incrby(key, amount)
|
||||
|
||||
## Decrements the integer value of a key by a given amount.
|
||||
func decrby(key: String, amount: int = 1) -> int:
|
||||
return _strings.decrby(key, amount)
|
||||
|
||||
## Gets all keys matching a pattern.
|
||||
func keys(pattern: String = "*") -> Array:
|
||||
return _strings.keys(pattern)
|
||||
|
||||
## Sets multiple keys to multiple values.
|
||||
func mset(dict: Dictionary) -> void:
|
||||
_strings.mset(dict)
|
||||
|
||||
## Gets the values of all specified keys.
|
||||
func mget(keys: Array) -> Array:
|
||||
return _strings.mget(keys)
|
||||
|
||||
## Appends a value to a key.
|
||||
func append(key: String, value: String) -> int:
|
||||
return _strings.append(key, value)
|
||||
|
||||
## Atomically sets a key to a value and returns the old value.
|
||||
func getset(key: String, value: Variant) -> Variant:
|
||||
return _strings.getset(key, value)
|
||||
|
||||
## Gets the length of the string value of a key.
|
||||
func strlen(key: String) -> int:
|
||||
return _strings.strlen(key)
|
||||
|
||||
## Sets a key to a value, only if the key does not exist.
|
||||
func setnx(key: String, value: Variant) -> int:
|
||||
return _strings.setnx(key, value)
|
||||
|
||||
## Renames a key to newkey, only if newkey does not exist.
|
||||
func rename(key: String, newkey: String) -> int:
|
||||
return _core.rename(key, newkey)
|
||||
|
||||
## Moves a key to another key.
|
||||
func move(key: String, newkey: String) -> int:
|
||||
return _core.move(key, newkey)
|
||||
|
||||
## Returns a random key from the database.
|
||||
func randomkey() -> String:
|
||||
var all_keys = _core._get_all_keys().keys()
|
||||
if all_keys.is_empty():
|
||||
return ""
|
||||
return all_keys.pick_random()
|
||||
|
||||
## Returns the number of keys in the database.
|
||||
func dbsize() -> int:
|
||||
return _core._get_all_keys().size()
|
||||
|
||||
## Adds the keyspace prefix to a key
|
||||
func ks(key: String) -> String:
|
||||
return _core.ks(key)
|
||||
|
||||
## Removes the keyspace prefix from a key if present, otherwise returns the key unchanged
|
||||
func rks(key: String) -> String:
|
||||
return _core.rks(key)
|
||||
|
||||
# Hashes
|
||||
## Sets the string value of a hash field.
|
||||
func hset(key: String, field: String, value) -> int:
|
||||
return _hashes.hset(key, field, value)
|
||||
|
||||
## Gets the value of a hash field.
|
||||
func hget(key: String, field: String, default_value: Variant = null):
|
||||
return _hashes.hget(key, field, default_value)
|
||||
|
||||
## Gets the values of all the given hash fields.
|
||||
func hmget(key: String, fields: Array) -> Array:
|
||||
return _hashes.hmget(key, fields)
|
||||
|
||||
## Sets multiple hash fields to multiple values.
|
||||
func hmset(key: String, field_value_pairs: Dictionary) -> void:
|
||||
_hashes.hmset(key, field_value_pairs)
|
||||
|
||||
## Increments the integer value of a hash field by the given amount.
|
||||
func hincrby(key: String, field: String, amount: int) -> Variant:
|
||||
return _hashes.hincrby(key, field, amount)
|
||||
|
||||
## Increments the float value of a hash field by the given amount.
|
||||
func hincrbyfloat(key: String, field: String, amount: float) -> Variant:
|
||||
return _hashes.hincrbyfloat(key, field, amount)
|
||||
|
||||
## Deletes one or more hash fields.
|
||||
func hdel(key: String, fields) -> int:
|
||||
return _hashes.hdel(key, fields)
|
||||
|
||||
## Gets all the fields and values in a hash.
|
||||
func hgetall(key: String) -> Dictionary:
|
||||
return _hashes.hgetall(key)
|
||||
|
||||
## Checks if a hash field exists.
|
||||
func hexists(key: String, field = null) -> bool:
|
||||
return _hashes.hexists(key, field)
|
||||
|
||||
## Gets all the fields in a hash.
|
||||
func hkeys(key: String) -> Array:
|
||||
return _hashes.hkeys(key)
|
||||
|
||||
## Gets all the values in a hash.
|
||||
func hvals(key: String) -> Array:
|
||||
return _hashes.hvals(key)
|
||||
|
||||
## Gets the number of fields in a hash.
|
||||
func hlen(key: String) -> int:
|
||||
return _hashes.hlen(key)
|
||||
|
||||
# Lists
|
||||
## Prepends one or multiple values to a list.
|
||||
func lpush(key: String, value) -> int:
|
||||
return _lists.lpush(key, value)
|
||||
|
||||
## Appends one or multiple values to a list.
|
||||
func rpush(key: String, value) -> int:
|
||||
return _lists.rpush(key, value)
|
||||
|
||||
## Removes and gets the first element in a list.
|
||||
func lpop(key: String):
|
||||
return _lists.lpop(key)
|
||||
|
||||
## Removes and gets the last element in a list.
|
||||
func rpop(key: String):
|
||||
return _lists.rpop(key)
|
||||
|
||||
## Gets the length of a list.
|
||||
func llen(key: String) -> int:
|
||||
return _lists.llen(key)
|
||||
|
||||
## Checks if a list exists.
|
||||
func lexists(key: String) -> bool:
|
||||
return _lists.lexists(key)
|
||||
|
||||
## Gets all elements from a list.
|
||||
func lget(key: String) -> Array:
|
||||
return _lists.lget(key)
|
||||
|
||||
## Gets a range of elements from a list.
|
||||
func lrange(key: String, start: int, stop: int) -> Array:
|
||||
return _lists.lrange(key, start, stop)
|
||||
|
||||
## Gets an element from a list by index.
|
||||
func lindex(key: String, index: int):
|
||||
return _lists.lindex(key, index)
|
||||
|
||||
## Sets the value of an element in a list by index.
|
||||
func lset(key: String, index: int, value) -> bool:
|
||||
return _lists.lset(key, index, value)
|
||||
|
||||
## Removes elements from a list.
|
||||
func lrem(key: String, count: int, value) -> int:
|
||||
return _lists.lrem(key, count, value)
|
||||
|
||||
## Trims a list to the specified range of indices.
|
||||
func ltrim(key: String, start: int, stop: int) -> bool:
|
||||
return _lists.ltrim(key, start, stop)
|
||||
|
||||
## Inserts a value into a list before or after a pivot value.
|
||||
func linsert(key: String, position: String, pivot, value) -> int:
|
||||
return _lists.linsert(key, position, pivot, value)
|
||||
|
||||
## Atomically returns and removes the first/last element of the list stored at source and pushes the element at the first/last element of the list stored at destination.
|
||||
func lmove(source: String, destination: String, from: String, to: String):
|
||||
return _lists.lmove(source, destination, from, to)
|
||||
|
||||
# Sets
|
||||
## Adds one or more members to a set.
|
||||
func sadd(key: String, member) -> int:
|
||||
return _sets.sadd(key, member)
|
||||
|
||||
## Removes one or more members from a set.
|
||||
func srem(key: String, member) -> int:
|
||||
return _sets.srem(key, member)
|
||||
|
||||
## Gets all the members in a set.
|
||||
func smembers(key: String) -> Array:
|
||||
return _sets.smembers(key)
|
||||
|
||||
## Checks if a member is in a set.
|
||||
func sismember(key: String, member) -> bool:
|
||||
return _sets.sismember(key, member)
|
||||
|
||||
## Gets the number of members in a set.
|
||||
func scard(key: String) -> int:
|
||||
return _sets.scard(key)
|
||||
|
||||
## Checks if a set exists.
|
||||
func sexists(key: String) -> bool:
|
||||
return _sets.sexists(key)
|
||||
|
||||
## Removes and returns a random member from a set.
|
||||
func spop(key: String):
|
||||
return _sets.spop(key)
|
||||
|
||||
## Moves a member from one set to another.
|
||||
func smove(source: String, destination: String, member) -> bool:
|
||||
return _sets.smove(source, destination, member)
|
||||
|
||||
## Returns the union of the sets stored at the given keys.
|
||||
func sunion(keys: Array) -> Array:
|
||||
return _sets.sunion(keys)
|
||||
|
||||
## Returns the intersection of the sets stored at the given keys.
|
||||
func sinter(keys: Array) -> Array:
|
||||
return _sets.sinter(keys)
|
||||
|
||||
## Returns the difference of the sets stored at the given keys.
|
||||
func sdiff(keys: Array) -> Array:
|
||||
return _sets.sdiff(keys)
|
||||
|
||||
## Stores the union of the sets at keys in the destination key.
|
||||
func sunionstore(destination: String, keys: Array) -> int:
|
||||
return _sets.sunionstore(destination, keys)
|
||||
|
||||
## Stores the intersection of the sets at keys in the destination key.
|
||||
func sinterstore(destination: String, keys: Array) -> int:
|
||||
return _sets.sinterstore(destination, keys)
|
||||
|
||||
## Stores the difference of the sets at keys in the destination key.
|
||||
func sdiffstore(destination: String, keys: Array) -> int:
|
||||
return _sets.sdiffstore(destination, keys)
|
||||
|
||||
## Returns one or more random members from the set at key.
|
||||
func srandmember(key: String, count: int = 1) -> Variant:
|
||||
return _sets.srandmember(key, count)
|
||||
|
||||
# Sorted Sets
|
||||
## Adds a member with a score to a sorted set.
|
||||
func zadd(key: String, member: String, score: int):
|
||||
return _sorted_sets.add(key, member, score)
|
||||
|
||||
## Checks if a sorted set exists.
|
||||
func zexists(key: String) -> bool:
|
||||
return _sorted_sets.zexists(key)
|
||||
|
||||
## Gets the number of members in a sorted set.
|
||||
func zcard(key: String) -> int:
|
||||
return _sorted_sets.zcard(key)
|
||||
|
||||
## Removes a member from a sorted set.
|
||||
func zrem(key: String, member: String):
|
||||
return _sorted_sets.remove(key, member)
|
||||
|
||||
## Gets members from a sorted set within a score range.
|
||||
func zrange(key: String, start, stop, withscores: bool = false):
|
||||
return _sorted_sets.zrange(key, start, stop, withscores)
|
||||
|
||||
## Gets members from a sorted set within a score range, in reverse order.
|
||||
func zrevrange(key: String, start, stop, withscores: bool = false):
|
||||
return _sorted_sets.zrevrange(key, start, stop, withscores)
|
||||
|
||||
## Removes and returns members with scores up to a certain value.
|
||||
func zpopready(key: String, now: int):
|
||||
return _sorted_sets.pop_ready(key, now)
|
||||
|
||||
## Returns the score of member in the sorted set at key.
|
||||
func zscore(key: String, member: String) -> Variant:
|
||||
return _sorted_sets.zscore(key, member)
|
||||
|
||||
## Returns the rank of member in the sorted set at key.
|
||||
func zrank(key: String, member: String) -> Variant:
|
||||
return _sorted_sets.zrank(key, member)
|
||||
|
||||
## Returns the rank of member in the sorted set at key, with scores ordered from high to low.
|
||||
func zrevrank(key: String, member: String) -> Variant:
|
||||
return _sorted_sets.zrevrank(key, member)
|
||||
|
||||
## Returns the number of elements in the sorted set at key with a score between min and max.
|
||||
func zcount(key: String, min_score, max_score) -> int:
|
||||
return _sorted_sets.zcount(key, min_score, max_score)
|
||||
|
||||
## Increments the score of member in the sorted set at key by increment.
|
||||
func zincrby(key: String, increment, member: String) -> Variant:
|
||||
return _sorted_sets.zincrby(key, increment, member)
|
||||
|
||||
## Returns a range of members in a sorted set, by score.
|
||||
func zrangebyscore(key: String, min_score, max_score, withscores: bool = false) -> Array:
|
||||
return _sorted_sets.zrangebyscore(key, min_score, max_score, withscores)
|
||||
|
||||
## Returns a range of members in a sorted set, by score, in reverse order.
|
||||
func zrevrangebyscore(key: String, min_score, max_score, withscores: bool = false) -> Array:
|
||||
return _sorted_sets.zrevrangebyscore(key, min_score, max_score, withscores)
|
||||
|
||||
## Computes the union of sorted sets and stores the result in a new key.
|
||||
func zunionstore(destination: String, keys: Array, aggregate: String = "SUM") -> int:
|
||||
return _sorted_sets.zunionstore(destination, keys, aggregate)
|
||||
|
||||
## Computes the intersection of sorted sets and stores the result in a new key.
|
||||
func zinterstore(destination: String, keys: Array, aggregate: String = "SUM") -> int:
|
||||
return _sorted_sets.zinterstore(destination, keys, aggregate)
|
||||
|
||||
# Pub/Sub
|
||||
## Posts a message to a channel.
|
||||
func publish(channel: String, message) -> void:
|
||||
_pubsub.publish.call_deferred(channel, message)
|
||||
|
||||
## Subscribes to a channel.
|
||||
func subscribe(channel: String, subscriber: Object) -> void:
|
||||
_pubsub.subscribe(channel, subscriber)
|
||||
|
||||
## Unsubscribes from a channel.
|
||||
func unsubscribe(channel: String, subscriber: Object) -> void:
|
||||
_pubsub.unsubscribe(channel, subscriber)
|
||||
|
||||
## Subscribes to channels matching a pattern.
|
||||
func psubscribe(pattern: String, subscriber: Object) -> void:
|
||||
_pubsub.psubscribe(pattern, subscriber)
|
||||
|
||||
## Unsubscribes from channels matching a pattern.
|
||||
func punsubscribe(pattern: String, subscriber: Object) -> void:
|
||||
_pubsub.punsubscribe(pattern, subscriber)
|
||||
|
||||
## Returns a list of all active channels.
|
||||
func list_channels() -> Array:
|
||||
return _pubsub.list_channels()
|
||||
|
||||
## Returns a list of subscribers for a given channel.
|
||||
func list_subscribers(channel: String) -> Array:
|
||||
return _pubsub.list_subscribers(channel)
|
||||
|
||||
## Returns a list of all active patterns.
|
||||
func list_patterns() -> Array:
|
||||
return _pubsub.list_patterns()
|
||||
|
||||
## Returns a list of subscribers for a given pattern.
|
||||
func list_pattern_subscribers(pattern: String) -> Array:
|
||||
return _pubsub.list_pattern_subscribers(pattern)
|
||||
|
||||
# Expiry
|
||||
## Sets a key's time to live in seconds.
|
||||
func expire(key: String, seconds: int) -> bool:
|
||||
return _expiry.expire(key, seconds)
|
||||
|
||||
## Gets the remaining time to live of a key.
|
||||
func ttl(key: String) -> int:
|
||||
return _expiry.ttl(key)
|
||||
|
||||
## Removes the expiration from a key.
|
||||
func persist(key: String) -> bool:
|
||||
return _expiry.persist(key)
|
||||
|
||||
## Purges all expired keys
|
||||
func purge_expired() -> void:
|
||||
_expiry._purge_expired()
|
||||
|
||||
# Admin
|
||||
## Deletes all keys from the database.
|
||||
func flushall() -> void:
|
||||
_core.flushall()
|
||||
|
||||
# Persistence
|
||||
## Registers a new persistence backend.
|
||||
func register_persistence_backend(name: String, backend: GedisPersistenceBackend) -> void:
|
||||
_persistence_backends[name] = backend
|
||||
|
||||
## Sets the default persistence backend.
|
||||
func set_default_persistence_backend(name: String) -> bool:
|
||||
if _persistence_backends.has(name):
|
||||
_default_persistence_backend = name
|
||||
return true
|
||||
return false
|
||||
|
||||
## Saves the current state to a file using the default persistence backend.
|
||||
func save(path: String, options: Dictionary = {}) -> int:
|
||||
if _default_persistence_backend.is_empty():
|
||||
register_persistence_backend("json", GedisJSONSnapshotBackend.new())
|
||||
set_default_persistence_backend("json")
|
||||
|
||||
var backend: GedisPersistenceBackend = _persistence_backends[_default_persistence_backend]
|
||||
var dump_options = options.duplicate()
|
||||
if dump_options.has("path"):
|
||||
dump_options.erase("path")
|
||||
|
||||
var data = _core.dump_all(dump_options)
|
||||
|
||||
var save_options = {"path": path}
|
||||
return backend.save(data, save_options)
|
||||
|
||||
## Loads the state from a file using the default persistence backend.
|
||||
func load(path: String, options: Dictionary = {}) -> int:
|
||||
if _default_persistence_backend.is_empty():
|
||||
register_persistence_backend("json", GedisJSONSnapshotBackend.new())
|
||||
set_default_persistence_backend("json")
|
||||
|
||||
var backend: GedisPersistenceBackend = _persistence_backends[_default_persistence_backend]
|
||||
var load_options = {"path": path}
|
||||
var data = backend.load(load_options)
|
||||
|
||||
if data.is_empty():
|
||||
return FAILED
|
||||
|
||||
_core.restore_all(data)
|
||||
return OK
|
||||
|
||||
## Dumps the entire dataset to a variable.
|
||||
func dump_all(options: Dictionary = {}) -> Dictionary:
|
||||
return _core.dump_all(options)
|
||||
|
||||
## Restores the entire dataset from a variable.
|
||||
func restore_all(data: Dictionary) -> void:
|
||||
_core.restore_all(data)
|
||||
|
||||
## Restores a key from a serialized value.
|
||||
func restore(key: String, data: String, backend: String = "") -> int:
|
||||
var persistence_backend: GedisPersistenceBackend
|
||||
if backend.is_empty():
|
||||
if _default_persistence_backend.is_empty():
|
||||
register_persistence_backend("json", GedisJSONSnapshotBackend.new())
|
||||
set_default_persistence_backend("json")
|
||||
persistence_backend = _persistence_backends[_default_persistence_backend]
|
||||
elif _persistence_backends.has(backend):
|
||||
persistence_backend = _persistence_backends[backend]
|
||||
else:
|
||||
return FAILED
|
||||
|
||||
var deserialized_data = persistence_backend.deserialize(data)
|
||||
if deserialized_data.is_empty():
|
||||
return FAILED
|
||||
|
||||
_core.restore_key(key, deserialized_data)
|
||||
return OK
|
||||
|
||||
# Debugger
|
||||
## Returns the type of the value stored at a key.
|
||||
func type(key: String) -> String:
|
||||
return _debugger_component.type(key)
|
||||
|
||||
## Returns a dictionary representation of the value stored at a key.
|
||||
func dump_key(key: String) -> Dictionary:
|
||||
return _debugger_component.dump(key)
|
||||
|
||||
## Returns a snapshot of the database for keys matching a pattern.
|
||||
func snapshot(pattern: String = "*") -> Dictionary:
|
||||
return _debugger_component.snapshot(pattern)
|
||||
|
||||
# Instance helpers
|
||||
## Sets the name for this Gedis instance.
|
||||
func set_instance_name(name: String) -> void:
|
||||
_instance_name = name
|
||||
|
||||
## Gets the name for this Gedis instance.
|
||||
func get_instance_name() -> String:
|
||||
return _instance_name
|
||||
|
||||
## Gets all active Gedis instances.
|
||||
static func get_all_instances() -> Array:
|
||||
var result: Array = []
|
||||
for inst in _instances:
|
||||
if is_instance_valid(inst):
|
||||
var info: Dictionary = {}
|
||||
info["id"] = inst._instance_id
|
||||
info["name"] = inst.name if inst.name else inst._instance_name
|
||||
info["object"] = inst
|
||||
result.append(info)
|
||||
return result
|
||||
|
||||
static func _on_debugger_message(message: String, data: Array) -> bool:
|
||||
return GedisDebugger._on_debugger_message(message, data)
|
||||
@ -1 +0,0 @@
|
||||
uid://7l48sw6l2lq3
|
||||
|
Before Width: | Height: | Size: 97 KiB |
@ -1,7 +0,0 @@
|
||||
[plugin]
|
||||
name="Gedis"
|
||||
description="Redis-like in-memory key-value store for Godot"
|
||||
author="NodotProject"
|
||||
version="0.1.9"
|
||||
script="plugin.gd"
|
||||
icon="icon.png"
|
||||
@ -1,412 +0,0 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const GedisDebuggerPanel = preload("res://addons/Gedis/debugger/gedis_debugger_panel.tscn")
|
||||
|
||||
class GedisDebuggerPlugin extends EditorDebuggerPlugin:
|
||||
var dashboard_tabs = {} # session_id -> dashboard_control
|
||||
var full_snapshot_data = {} # session_id -> full_snapshot_data
|
||||
var pending_requests = {} # session_id -> { "command": String, "instance_id": int, "context": any }
|
||||
var _pubsub_events = {} # session_id -> []
|
||||
|
||||
func _has_capture(capture):
|
||||
return capture == "gedis"
|
||||
|
||||
func _capture(message, data, session_id):
|
||||
if not _pubsub_events.has(session_id):
|
||||
_pubsub_events[session_id] = []
|
||||
# message is the full message string received by the editor (e.g. "gedis:instances_data")
|
||||
# data is the Array payload sent from the game. Engines and editors may wrap values
|
||||
# differently (sometimes the payload is passed as a single element array), so normalize.
|
||||
var parts = message.split(":")
|
||||
var kind = parts[1] if parts.size() > 1 else ""
|
||||
|
||||
match kind:
|
||||
"ping":
|
||||
_request_instances_update(session_id)
|
||||
return true
|
||||
"instances_data":
|
||||
# instances may be either sent as the array itself or wrapped as [instances]
|
||||
var instances = data
|
||||
if data.size() == 1 and typeof(data[0]) == TYPE_ARRAY:
|
||||
instances = data[0]
|
||||
_update_instances(instances, session_id)
|
||||
return true
|
||||
"snapshot_data":
|
||||
# snapshot is expected to be a Dictionary; it may be wrapped in an Array
|
||||
var snapshot = data[0] if data.size() > 0 else {}
|
||||
_update_snapshot_data(snapshot, session_id)
|
||||
return true
|
||||
"key_value_data":
|
||||
# key/value payload may be wrapped similarly
|
||||
var kv = data[0] if data.size() > 0 else {}
|
||||
_update_key_value_data(kv, session_id)
|
||||
return true
|
||||
"pubsub_data":
|
||||
var channels_data = data[0] if data.size() > 0 else {}
|
||||
var patterns_data = data[1] if data.size() > 1 else {}
|
||||
_update_pubsub_tree(session_id, channels_data, patterns_data)
|
||||
return true
|
||||
"pubsub_event":
|
||||
if not session_id in _pubsub_events:
|
||||
_pubsub_events[session_id] = []
|
||||
_pubsub_events[session_id].append({"message": data[0], "data": [data[1], data[2]]})
|
||||
_populate_pubsub_events(session_id)
|
||||
_fetch_pubsub_for_selected_instance(session_id) # Refresh tree on event
|
||||
return true
|
||||
return false
|
||||
|
||||
func _setup_session(session_id):
|
||||
var dashboard = GedisDebuggerPanel.instantiate()
|
||||
dashboard.name = "Gedis"
|
||||
dashboard.plugin = self
|
||||
|
||||
var session = get_session(session_id)
|
||||
session.started.connect(func(): _on_session_started(session_id))
|
||||
session.stopped.connect(func(): _on_session_stopped(session_id))
|
||||
session.add_session_tab(dashboard)
|
||||
|
||||
dashboard_tabs[session_id] = dashboard
|
||||
_pubsub_events[session_id] = []
|
||||
|
||||
func _on_session_started(session_id):
|
||||
full_snapshot_data[session_id] = {}
|
||||
_clear_views(session_id)
|
||||
_populate_pubsub_events(session_id)
|
||||
_update_pubsub_tree(session_id, {}, {})
|
||||
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
|
||||
var instance_selector = dashboard.find_child("instance_selector", true, false)
|
||||
if instance_selector:
|
||||
instance_selector.item_selected.connect(func(index): _on_instance_selected(index, session_id))
|
||||
|
||||
var refresh_button = dashboard.find_child("RefreshButton", true, false)
|
||||
if refresh_button:
|
||||
refresh_button.pressed.connect(func(): _request_instances_update(session_id))
|
||||
|
||||
var fetch_keys_button = dashboard.find_child("FetchKeysButton", true, false)
|
||||
if fetch_keys_button:
|
||||
fetch_keys_button.pressed.connect(func(): _fetch_keys_for_selected_instance(session_id))
|
||||
|
||||
var filter_button = dashboard.find_child("filter_button", true, false)
|
||||
if filter_button:
|
||||
filter_button.pressed.connect(func(): _on_filter_pressed(session_id))
|
||||
|
||||
var key_list = dashboard.find_child("key_list", true, false)
|
||||
if key_list:
|
||||
key_list.item_selected.connect(func(): _on_key_selected(session_id))
|
||||
|
||||
_request_instances_update(session_id)
|
||||
|
||||
func _on_session_stopped(session_id):
|
||||
if session_id in dashboard_tabs:
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
var status_label = dashboard.find_child("status_label", true, false)
|
||||
if status_label:
|
||||
status_label.text = "Game disconnected"
|
||||
status_label.add_theme_color_override("font_color", Color.RED)
|
||||
|
||||
func _request_instances_update(session_id):
|
||||
var session = get_session(session_id)
|
||||
if session and session.is_active():
|
||||
session.send_message("gedis:request_instances", [])
|
||||
|
||||
func _update_instances(instances_data, session_id):
|
||||
if not session_id in dashboard_tabs:
|
||||
return
|
||||
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
var status_label = dashboard.find_child("status_label", true, false)
|
||||
var instance_selector = dashboard.find_child("instance_selector", true, false)
|
||||
|
||||
if not instance_selector:
|
||||
print("Warning: instance_selector not found in dashboard")
|
||||
return
|
||||
|
||||
instance_selector.clear()
|
||||
|
||||
if instances_data.size() > 0:
|
||||
status_label.text = "Connected - Found %d Gedis instance(s)" % instances_data.size()
|
||||
status_label.add_theme_color_override("font_color", Color.GREEN)
|
||||
|
||||
for instance_info in instances_data:
|
||||
var name = instance_info.get("name", "Gedis_%d" % instance_info.get("id", -1))
|
||||
var id = int(instance_info["id"]) if instance_info.has("id") else -1
|
||||
instance_selector.add_item(name, id)
|
||||
|
||||
if instance_selector.get_item_count() > 0:
|
||||
instance_selector.select(0)
|
||||
# The select() method does not trigger the item_selected signal, so we must call the handler manually.
|
||||
_fetch_keys_for_selected_instance(session_id)
|
||||
_fetch_pubsub_for_selected_instance(session_id)
|
||||
else:
|
||||
status_label.text = "No Gedis instances found in running game"
|
||||
status_label.add_theme_color_override("font_color", Color.ORANGE)
|
||||
_clear_views(session_id)
|
||||
|
||||
func _on_instance_selected(_index, session_id):
|
||||
_fetch_keys_for_selected_instance(session_id)
|
||||
_fetch_pubsub_for_selected_instance(session_id)
|
||||
|
||||
func _fetch_keys_for_selected_instance(session_id):
|
||||
if not session_id in dashboard_tabs:
|
||||
return
|
||||
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
var instance_selector = dashboard.find_child("instance_selector", true, false)
|
||||
if not instance_selector or instance_selector.get_selected() < 0:
|
||||
return
|
||||
|
||||
var instance_id = instance_selector.get_item_id(instance_selector.get_selected())
|
||||
var session = get_session(session_id)
|
||||
pending_requests[session_id] = {"command": "snapshot", "instance_id": instance_id}
|
||||
if session and session.is_active():
|
||||
session.send_message("gedis:request_instance_data", [instance_id, "snapshot", "*"])
|
||||
|
||||
func _fetch_pubsub_for_selected_instance(session_id):
|
||||
if not session_id in dashboard_tabs:
|
||||
return
|
||||
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
var instance_selector = dashboard.find_child("instance_selector", true, false)
|
||||
if not instance_selector or instance_selector.get_selected() < 0:
|
||||
return
|
||||
|
||||
var instance_id = instance_selector.get_item_id(instance_selector.get_selected())
|
||||
var session = get_session(session_id)
|
||||
pending_requests[session_id] = {"command": "pubsub", "instance_id": instance_id}
|
||||
if session and session.is_active():
|
||||
session.send_message("gedis:request_instance_data", [instance_id, "pubsub"])
|
||||
|
||||
func _on_filter_pressed(session_id):
|
||||
if not session_id in dashboard_tabs:
|
||||
return
|
||||
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
var search_box = dashboard.find_child("search_box", true, false)
|
||||
var filter_text = search_box.text if search_box else ""
|
||||
|
||||
_populate_key_list(session_id, filter_text)
|
||||
|
||||
|
||||
func _update_snapshot_data(snapshot_data, session_id):
|
||||
if not session_id in dashboard_tabs:
|
||||
return
|
||||
|
||||
full_snapshot_data[session_id] = snapshot_data
|
||||
_populate_key_list(session_id)
|
||||
|
||||
func _populate_key_list(session_id, filter_text = ""):
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
var key_list = dashboard.find_child("key_list", true, false)
|
||||
|
||||
if not key_list:
|
||||
return
|
||||
|
||||
key_list.clear()
|
||||
var root = key_list.create_item()
|
||||
|
||||
var data_to_display = full_snapshot_data.get(session_id, {})
|
||||
|
||||
var regex = RegEx.new()
|
||||
if not filter_text.is_empty():
|
||||
var pattern = filter_text.replace("*", ".*").replace("?", ".")
|
||||
regex.compile(pattern)
|
||||
|
||||
for redis_key in data_to_display.keys():
|
||||
if filter_text.is_empty() or regex.search(redis_key):
|
||||
var key_info = data_to_display[redis_key]
|
||||
var item = key_list.create_item(root)
|
||||
item.set_text(0, redis_key)
|
||||
item.set_text(1, key_info.get("type", "UNKNOWN"))
|
||||
var ttl_value = key_info.get("ttl", -1)
|
||||
if ttl_value == -1:
|
||||
item.set_text(2, "∞")
|
||||
elif ttl_value == -2:
|
||||
item.set_text(2, "EXPIRED")
|
||||
else:
|
||||
item.set_text(2, str(ttl_value) + "s")
|
||||
|
||||
func _update_key_value_data(key_value_data, session_id):
|
||||
if not session_id in dashboard_tabs:
|
||||
return
|
||||
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
var key_value_view = dashboard.find_child("key_value_view", true, false)
|
||||
|
||||
if not key_value_view:
|
||||
return
|
||||
|
||||
if key_value_data is Dictionary and "value" in key_value_data:
|
||||
key_value_view.text = var_to_str(key_value_data.value)
|
||||
else:
|
||||
key_value_view.text = var_to_str(key_value_data)
|
||||
|
||||
func _on_key_selected(session_id):
|
||||
if not session_id in dashboard_tabs:
|
||||
return
|
||||
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
var key_list = dashboard.find_child("key_list", true, false)
|
||||
var key_value_view = dashboard.find_child("key_value_view", true, false)
|
||||
var instance_selector = dashboard.find_child("instance_selector", true, false)
|
||||
|
||||
if not key_list or not key_value_view or not instance_selector:
|
||||
return
|
||||
|
||||
var selected_item = key_list.get_selected()
|
||||
if not selected_item:
|
||||
key_value_view.text = ""
|
||||
return
|
||||
|
||||
var selected_key = selected_item.get_text(0)
|
||||
if instance_selector.get_selected() >= 0:
|
||||
var instance_id = instance_selector.get_item_id(instance_selector.get_selected())
|
||||
var session = get_session(session_id)
|
||||
pending_requests[session_id] = {"command": "dump", "instance_id": instance_id, "key": selected_key}
|
||||
if session and session.is_active():
|
||||
session.send_message("gedis:request_instance_data", [instance_id, "dump", selected_key])
|
||||
|
||||
key_value_view.editable = false
|
||||
|
||||
|
||||
func _on_edit_pressed(session_id):
|
||||
if not session_id in dashboard_tabs:
|
||||
return
|
||||
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
var key_value_view = dashboard.find_child("key_value_view", true, false)
|
||||
var edit_button = dashboard.find_child("edit_button", true, false)
|
||||
var save_button = dashboard.find_child("save_button", true, false)
|
||||
|
||||
if not key_value_view or not edit_button or not save_button:
|
||||
return
|
||||
|
||||
key_value_view.editable = true
|
||||
save_button.disabled = false
|
||||
edit_button.disabled = true
|
||||
|
||||
func _on_save_pressed(session_id):
|
||||
if not session_id in dashboard_tabs:
|
||||
return
|
||||
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
var key_list = dashboard.find_child("key_list", true, false)
|
||||
var key_value_view = dashboard.find_child("key_value_view", true, false)
|
||||
var instance_selector = dashboard.find_child("instance_selector", true, false)
|
||||
var edit_button = dashboard.find_child("edit_button", true, false)
|
||||
var save_button = dashboard.find_child("save_button", true, false)
|
||||
|
||||
if not key_list or not key_value_view or not instance_selector or not edit_button or not save_button:
|
||||
return
|
||||
|
||||
var selected_item = key_list.get_selected()
|
||||
if not selected_item or instance_selector.get_selected() < 0:
|
||||
return
|
||||
|
||||
var instance_id = instance_selector.get_item_id(instance_selector.get_selected())
|
||||
var key = selected_item.get_text(0)
|
||||
var new_value_text = key_value_view.text
|
||||
|
||||
var json = JSON.new()
|
||||
var error = json.parse(new_value_text)
|
||||
var new_value
|
||||
if error == OK:
|
||||
new_value = json.get_data()
|
||||
else:
|
||||
# Fallback for non-JSON strings
|
||||
new_value = new_value_text
|
||||
|
||||
var session = get_session(session_id)
|
||||
if session and session.is_active():
|
||||
pending_requests[session_id] = {"command": "set", "instance_id": instance_id, "key": key}
|
||||
session.send_message("gedis:request_instance_data", [instance_id, "set", key, new_value])
|
||||
|
||||
key_value_view.editable = false
|
||||
save_button.disabled = true
|
||||
edit_button.disabled = false
|
||||
|
||||
func _clear_views(session_id):
|
||||
if not session_id in dashboard_tabs:
|
||||
return
|
||||
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
var key_list = dashboard.find_child("key_list", true, false)
|
||||
var key_value_view = dashboard.find_child("key_value_view", true, false)
|
||||
var edit_button = dashboard.find_child("edit_button", true, false)
|
||||
var save_button = dashboard.find_child("save_button", true, false)
|
||||
|
||||
if key_list:
|
||||
key_list.clear()
|
||||
if key_value_view:
|
||||
key_value_view.text = ""
|
||||
if edit_button:
|
||||
edit_button.disabled = true
|
||||
if save_button:
|
||||
save_button.disabled = true
|
||||
|
||||
func _populate_pubsub_events(session_id):
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
var events_tree = dashboard.find_child("PubSubEventsTree", true, false)
|
||||
if not events_tree:
|
||||
return
|
||||
events_tree.clear()
|
||||
var root = events_tree.create_item()
|
||||
for event in _pubsub_events.get(session_id, []):
|
||||
var event_item = events_tree.create_item(root)
|
||||
event_item.set_text(0, event.message)
|
||||
event_item.set_text(1, JSON.stringify(event.data))
|
||||
|
||||
func _update_pubsub_tree(session_id, channels_data, patterns_data):
|
||||
var dashboard = dashboard_tabs[session_id]
|
||||
var pub_sub_tree = dashboard.find_child("PubSubTree", true, false)
|
||||
if not pub_sub_tree:
|
||||
return
|
||||
|
||||
pub_sub_tree.clear()
|
||||
var root = pub_sub_tree.create_item()
|
||||
|
||||
var channels_item = pub_sub_tree.create_item(root)
|
||||
channels_item.set_text(0, "Channels")
|
||||
for channel_name in channels_data:
|
||||
var subscribers = channels_data[channel_name]
|
||||
var channel_item = pub_sub_tree.create_item(channels_item)
|
||||
channel_item.set_text(0, channel_name)
|
||||
for sub in subscribers:
|
||||
var sub_item = pub_sub_tree.create_item(channel_item)
|
||||
sub_item.set_text(0, str(sub))
|
||||
|
||||
var patterns_item = pub_sub_tree.create_item(root)
|
||||
patterns_item.set_text(0, "Patterns")
|
||||
for pattern_name in patterns_data:
|
||||
var subscribers = patterns_data[pattern_name]
|
||||
var pattern_item = pub_sub_tree.create_item(patterns_item)
|
||||
pattern_item.set_text(0, pattern_name)
|
||||
for sub in subscribers:
|
||||
var sub_item = pub_sub_tree.create_item(pattern_item)
|
||||
sub_item.set_text(0, str(sub))
|
||||
|
||||
func send_message_to_game(message: String, data: Array):
|
||||
var session_id = get_current_session_id()
|
||||
if session_id != -1:
|
||||
var session = get_session(session_id)
|
||||
if session and session.is_active():
|
||||
session.send_message("gedis:" + message, data)
|
||||
|
||||
func get_current_session_id():
|
||||
for session_id in dashboard_tabs:
|
||||
var session = get_session(session_id)
|
||||
if session and session.is_active():
|
||||
return session_id
|
||||
return -1
|
||||
|
||||
var debugger_plugin
|
||||
|
||||
func _enter_tree():
|
||||
debugger_plugin = GedisDebuggerPlugin.new()
|
||||
add_debugger_plugin(debugger_plugin)
|
||||
|
||||
func _exit_tree():
|
||||
remove_debugger_plugin(debugger_plugin)
|
||||
debugger_plugin = null
|
||||
@ -1 +0,0 @@
|
||||
uid://bad4nw3ewpjx6
|
||||
@ -1,274 +0,0 @@
|
||||
extends Node
|
||||
class_name GedisQueue
|
||||
|
||||
## Manages job queues and workers.
|
||||
##
|
||||
## This class provides a high-level interface for creating and managing job queues
|
||||
## that are processed by workers. It uses Gedis (a Godot Redis-like in-memory
|
||||
## data structure server) for storing job information and queue state.
|
||||
|
||||
## Emitted when a job is completed successfully.
|
||||
signal completed(job: GedisJob, return_value)
|
||||
## Emitted when a job fails.
|
||||
signal failed(job: GedisJob, error_message: String)
|
||||
## Emitted when a job's progress is updated.
|
||||
signal progress(job: GedisJob, value: float)
|
||||
|
||||
const QUEUE_PREFIX = "gedis_queue:"
|
||||
|
||||
var max_completed_jobs := 0
|
||||
var max_failed_jobs := 0
|
||||
|
||||
const STATUS_WAITING = "waiting"
|
||||
const STATUS_ACTIVE = "active"
|
||||
const STATUS_COMPLETED = "completed"
|
||||
const STATUS_FAILED = "failed"
|
||||
|
||||
var _gedis: Gedis
|
||||
var _workers: Array[GedisWorker] = []
|
||||
|
||||
## Sets up the GedisQueue with a Gedis instance.
|
||||
##
|
||||
## If no Gedis instance is provided, a new one will be created automatically
|
||||
## when needed.
|
||||
##
|
||||
## @param gedis_instance The Gedis instance to use.
|
||||
func setup(gedis_instance: Gedis = null):
|
||||
if gedis_instance:
|
||||
_gedis = gedis_instance
|
||||
else:
|
||||
_gedis = Gedis.new()
|
||||
_gedis.name = "Gedis"
|
||||
add_child(_gedis)
|
||||
|
||||
## Adds a new job to a queue.
|
||||
##
|
||||
## @param queue_name The name of the queue to add the job to.
|
||||
## @param job_data A dictionary containing the data for the job.
|
||||
## @param opts A dictionary of options for the job.
|
||||
## "add_to_front": (bool) If true, adds the job to the front of the queue.
|
||||
## @return The newly created GedisJob.
|
||||
func add(queue_name: String, job_data: Dictionary, opts: Dictionary = {}) -> GedisJob:
|
||||
_ensure_gedis_instance()
|
||||
|
||||
var job_id = _generate_job_id()
|
||||
var job_key = _get_job_key(queue_name, job_id)
|
||||
var job = GedisJob.new(self, queue_name, job_id, job_data)
|
||||
|
||||
var job_hash = {
|
||||
"id": job_id,
|
||||
"data": job_data,
|
||||
"status": STATUS_WAITING,
|
||||
"progress": 0.0
|
||||
}
|
||||
|
||||
for key in job_hash:
|
||||
_gedis.hset(job_key, key, job_hash[key])
|
||||
|
||||
if opts.get("add_to_front", false):
|
||||
_gedis.lpush(_get_queue_key(queue_name, STATUS_WAITING), job_id)
|
||||
else:
|
||||
_gedis.rpush(_get_queue_key(queue_name, STATUS_WAITING), job_id)
|
||||
|
||||
_gedis.publish(_get_event_channel(queue_name, "added"), {"job_id": job_id, "data": job_data})
|
||||
|
||||
return job
|
||||
|
||||
## Retrieves a job from a queue by its ID.
|
||||
##
|
||||
## @param queue_name The name of the queue.
|
||||
## @param job_id The ID of the job to retrieve.
|
||||
## @return The GedisJob if found, otherwise null.
|
||||
func get_job(queue_name: String, job_id: String) -> GedisJob:
|
||||
var job_key = _get_job_key(queue_name, job_id)
|
||||
var job_hash = _gedis.hgetall(job_key)
|
||||
|
||||
if job_hash.is_empty():
|
||||
return null
|
||||
|
||||
var job_data = job_hash.get("data", {})
|
||||
var job_status = job_hash.get("status", GedisQueue.STATUS_WAITING)
|
||||
var job = GedisJob.new(self, queue_name, job_id, job_data, job_status)
|
||||
return job
|
||||
|
||||
## Retrieves a list of jobs from a queue.
|
||||
##
|
||||
## @param queue_name The name of the queue.
|
||||
## @param types An array of job statuses to retrieve (e.g., ["waiting", "active"]).
|
||||
## @param start The starting index.
|
||||
## @param end The ending index.
|
||||
## @param asc Whether to sort in ascending order (currently unused).
|
||||
## @return An array of GedisJob objects.
|
||||
func get_jobs(queue_name: String, types: Array, start: int = 0, end: int = -1, asc: bool = false) -> Array[GedisJob]:
|
||||
var jobs: Array[GedisJob] = []
|
||||
for type in types:
|
||||
var queue_key = _get_queue_key(queue_name, type)
|
||||
var job_ids = _gedis.lrange(queue_key, start, end)
|
||||
for job_id in job_ids:
|
||||
var job = get_job(queue_name, job_id)
|
||||
if job:
|
||||
jobs.append(job)
|
||||
return jobs
|
||||
|
||||
## Pauses a queue.
|
||||
##
|
||||
## When a queue is paused, workers will not process any new jobs from it.
|
||||
##
|
||||
## @param queue_name The name of the queue to pause.
|
||||
func pause(queue_name: String) -> void:
|
||||
_ensure_gedis_instance()
|
||||
|
||||
var state_key = _get_state_key(queue_name)
|
||||
_gedis.hset(state_key, "paused", "1")
|
||||
|
||||
## Resumes a paused queue.
|
||||
##
|
||||
## @param queue_name The name of the queue to resume.
|
||||
func resume(queue_name: String) -> void:
|
||||
_ensure_gedis_instance()
|
||||
|
||||
var state_key = _get_state_key(queue_name)
|
||||
_gedis.hdel(state_key, "paused")
|
||||
|
||||
## Checks if a queue is paused.
|
||||
##
|
||||
## @param queue_name The name of the queue.
|
||||
## @return True if the queue is paused, otherwise false.
|
||||
func is_paused(queue_name: String) -> bool:
|
||||
_ensure_gedis_instance()
|
||||
|
||||
var state_key = _get_state_key(queue_name)
|
||||
return _gedis.hexists(state_key, "paused")
|
||||
|
||||
## Updates the progress of a job.
|
||||
##
|
||||
## @param queue_name The name of the queue.
|
||||
## @param job_id The ID of the job.
|
||||
## @param value The new progress value (0.0 to 1.0).
|
||||
func update_job_progress(queue_name: String, job_id: String, value: float):
|
||||
_ensure_gedis_instance()
|
||||
|
||||
var job_key = _get_job_key(queue_name, job_id)
|
||||
_gedis.hset(job_key, "progress", value)
|
||||
_gedis.publish(_get_event_channel(queue_name, "progress"), {"job_id": job_id, "progress": value})
|
||||
progress.emit(get_job(queue_name, job_id), value)
|
||||
|
||||
## Removes a job from a queue.
|
||||
##
|
||||
## @param queue_name The name of the queue.
|
||||
## @param job_id The ID of the job to remove.
|
||||
func remove_job(queue_name: String, job_id: String):
|
||||
_ensure_gedis_instance()
|
||||
|
||||
var job_key = _get_job_key(queue_name, job_id)
|
||||
_gedis.del(job_key)
|
||||
|
||||
# Remove the job ID from all possible status lists
|
||||
for status in [STATUS_WAITING, STATUS_ACTIVE, STATUS_COMPLETED, STATUS_FAILED]:
|
||||
var queue_key = _get_queue_key(queue_name, status)
|
||||
_gedis.lrem(queue_key, 0, job_id)
|
||||
|
||||
func _get_queue_key(queue_name: String, status: String = STATUS_WAITING) -> String:
|
||||
return "%s%s:%s" % [QUEUE_PREFIX, queue_name, status]
|
||||
|
||||
func _get_job_key(queue_name: String, job_id: String) -> String:
|
||||
return QUEUE_PREFIX + queue_name + ":job:" + job_id
|
||||
|
||||
func _get_state_key(queue_name: String) -> String:
|
||||
return QUEUE_PREFIX + queue_name + ":state"
|
||||
|
||||
func _get_event_channel(queue_name: String, event: String) -> String:
|
||||
return "%s%s:events:%s" % [QUEUE_PREFIX, queue_name, event]
|
||||
|
||||
func _generate_job_id() -> String:
|
||||
var t = Time.get_unix_time_from_system()
|
||||
var r = randi() % 1000
|
||||
return "%s-%s" % [t, r]
|
||||
|
||||
func _ensure_gedis_instance():
|
||||
if not _gedis:
|
||||
var gedis_instance = Gedis.new()
|
||||
gedis_instance.name = "Gedis"
|
||||
add_child(gedis_instance)
|
||||
setup(gedis_instance)
|
||||
|
||||
## Starts a worker to process jobs from a queue.
|
||||
##
|
||||
## @param queue_name The name of the queue to process.
|
||||
## @param processor A callable that will be executed for each job.
|
||||
## @return The newly created GedisWorker.
|
||||
func process(queue_name: String, processor: Callable, p_batch_size: int = 1) -> GedisWorker:
|
||||
var worker = GedisWorker.new(self, queue_name, processor, p_batch_size)
|
||||
add_child(worker)
|
||||
_workers.append(worker)
|
||||
worker.start()
|
||||
return worker
|
||||
|
||||
## Closes all workers for a specific queue.
|
||||
##
|
||||
## @param queue_name The name of the queue.
|
||||
func close(queue_name: String) -> void:
|
||||
var workers_to_remove: Array[GedisWorker] = []
|
||||
for worker in _workers:
|
||||
if worker._queue_name == queue_name:
|
||||
workers_to_remove.append(worker)
|
||||
|
||||
for worker in workers_to_remove:
|
||||
worker.close()
|
||||
_workers.erase(worker)
|
||||
worker.queue_free()
|
||||
|
||||
func _enter_tree() -> void:
|
||||
if !_gedis:
|
||||
var gedis_instance = Gedis.new()
|
||||
gedis_instance.name = "Gedis"
|
||||
add_child(gedis_instance)
|
||||
_gedis = gedis_instance
|
||||
|
||||
func _exit_tree():
|
||||
for worker in _workers:
|
||||
if is_instance_valid(worker):
|
||||
worker.close()
|
||||
|
||||
## Marks a job as completed.
|
||||
##
|
||||
## @param job The job to mark as completed.
|
||||
## @param return_value The return value of the job.
|
||||
func _job_completed(job: GedisJob, return_value):
|
||||
_ensure_gedis_instance()
|
||||
var job_key = _get_job_key(job.queue_name, job.id)
|
||||
_gedis.lrem(_get_queue_key(job.queue_name, STATUS_ACTIVE), 1, job.id)
|
||||
|
||||
completed.emit(job, return_value)
|
||||
_gedis.publish(_get_event_channel(job.queue_name, "completed"), {"job_id": job.id, "return_value": return_value})
|
||||
|
||||
if max_completed_jobs == 0:
|
||||
_gedis.del(job_key)
|
||||
else:
|
||||
_gedis.hset(job_key, "status", STATUS_COMPLETED)
|
||||
_gedis.hset(job_key, "returnvalue", return_value)
|
||||
_gedis.lpush(_get_queue_key(job.queue_name, STATUS_COMPLETED), job.id)
|
||||
if max_completed_jobs > 0:
|
||||
_gedis.ltrim(_get_queue_key(job.queue_name, STATUS_COMPLETED), 0, max_completed_jobs - 1)
|
||||
|
||||
|
||||
## Marks a job as failed.
|
||||
##
|
||||
## @param job The job to mark as failed.
|
||||
## @param error_message The error message.
|
||||
func _job_failed(job: GedisJob, error_message: String):
|
||||
_ensure_gedis_instance()
|
||||
var job_key = _get_job_key(job.queue_name, job.id)
|
||||
_gedis.lrem(_get_queue_key(job.queue_name, STATUS_ACTIVE), 1, job.id)
|
||||
|
||||
failed.emit(job, error_message)
|
||||
_gedis.publish(_get_event_channel(job.queue_name, "failed"), {"job_id": job.id, "error_message": error_message})
|
||||
|
||||
if max_failed_jobs == 0:
|
||||
_gedis.del(job_key)
|
||||
else:
|
||||
_gedis.hset(job_key, "status", STATUS_FAILED)
|
||||
_gedis.hset(job_key, "failed_reason", error_message)
|
||||
_gedis.lpush(_get_queue_key(job.queue_name, STATUS_FAILED), job.id)
|
||||
if max_failed_jobs > 0:
|
||||
_gedis.ltrim(_get_queue_key(job.queue_name, STATUS_FAILED), 0, max_failed_jobs - 1)
|
||||
@ -1 +0,0 @@
|
||||
uid://cbcpxfgtw831t
|
||||
@ -1,51 +0,0 @@
|
||||
extends RefCounted
|
||||
|
||||
class_name GedisJob
|
||||
|
||||
## Represents a job in a GedisQueue.
|
||||
##
|
||||
## This class holds information about a job, such as its ID, data, and status.
|
||||
## It also provides methods for interacting with the job, such as updating its
|
||||
## progress and removing it from the queue.
|
||||
|
||||
var id: String
|
||||
var data: Dictionary
|
||||
var queue_name: String
|
||||
var status: String
|
||||
|
||||
var _gedis_queue
|
||||
|
||||
func _init(p_gedis_queue, p_queue_name: String, p_id: String, p_data: Dictionary, p_status: String = GedisQueue.STATUS_WAITING):
|
||||
_gedis_queue = p_gedis_queue
|
||||
queue_name = p_queue_name
|
||||
id = p_id
|
||||
data = p_data
|
||||
status = p_status
|
||||
|
||||
## Updates the progress of the job.
|
||||
##
|
||||
## @param value The new progress value (0.0 to 1.0).
|
||||
func progress(value: float) -> void:
|
||||
_gedis_queue.update_job_progress(queue_name, id, value)
|
||||
|
||||
## Removes the job from the queue.
|
||||
func remove() -> void:
|
||||
_gedis_queue.remove_job(queue_name, id)
|
||||
|
||||
## Marks the job as completed.
|
||||
##
|
||||
## This should be called by a worker's processor function when the job has
|
||||
## been successfully processed.
|
||||
##
|
||||
## @param return_value An optional value to store as the result of the job.
|
||||
func complete(return_value: Variant = null) -> void:
|
||||
_gedis_queue._job_completed(self, return_value)
|
||||
|
||||
## Marks the job as failed.
|
||||
##
|
||||
## This should be called by a worker's processor function when the job has
|
||||
## failed to be processed.
|
||||
##
|
||||
## @param error_message The error message to store for the failed job.
|
||||
func fail(error_message: String) -> void:
|
||||
_gedis_queue._job_failed(self, error_message)
|
||||
@ -1 +0,0 @@
|
||||
uid://3qjcot47lxgw
|
||||
@ -1,106 +0,0 @@
|
||||
extends Node
|
||||
|
||||
class_name GedisWorker
|
||||
|
||||
## Processes jobs from a GedisQueue.
|
||||
##
|
||||
## A worker is responsible for fetching jobs from a specific queue and executing
|
||||
## a processor function for each job.
|
||||
##
|
||||
## The processor function receives the job and is responsible for calling
|
||||
## `job.complete()` or `job.fail()` to finish the job. The worker itself does
|
||||
## not handle the return value of the processor.
|
||||
|
||||
signal completed(job: GedisJob, return_value)
|
||||
signal failed(job: GedisJob, error_message: String)
|
||||
signal progress(job: GedisJob, value: float)
|
||||
signal _batch_completed
|
||||
|
||||
var _gedis_queue: GedisQueue
|
||||
var _queue_name: String
|
||||
var _processor: Callable
|
||||
var _is_running = false
|
||||
var _gedis: Gedis
|
||||
var _jobs_in_progress = {}
|
||||
|
||||
var batch_size = 1
|
||||
|
||||
func _init(p_gedis_queue: GedisQueue, p_queue_name: String, p_processor: Callable, p_batch_size: int = 1):
|
||||
_gedis_queue = p_gedis_queue
|
||||
_gedis = _gedis_queue._gedis
|
||||
_queue_name = p_queue_name
|
||||
_processor = p_processor
|
||||
batch_size = p_batch_size
|
||||
|
||||
_gedis_queue.completed.connect(_on_job_completed)
|
||||
_gedis_queue.failed.connect(_on_job_failed)
|
||||
_gedis_queue.progress.connect(func(job: GedisJob, value: float): progress.emit(job, value))
|
||||
|
||||
## Starts the worker.
|
||||
func start():
|
||||
_is_running = true
|
||||
_process_jobs()
|
||||
|
||||
## Stops the worker.
|
||||
func close():
|
||||
_is_running = false
|
||||
|
||||
func _on_job_completed(job: GedisJob, return_value):
|
||||
if _jobs_in_progress.has(job.id):
|
||||
_jobs_in_progress.erase(job.id)
|
||||
if _jobs_in_progress.is_empty():
|
||||
_batch_completed.emit()
|
||||
completed.emit(job, return_value)
|
||||
|
||||
func _on_job_failed(job: GedisJob, error_message: String):
|
||||
if _jobs_in_progress.has(job.id):
|
||||
_jobs_in_progress.erase(job.id)
|
||||
if _jobs_in_progress.is_empty():
|
||||
_batch_completed.emit()
|
||||
failed.emit(job, error_message)
|
||||
|
||||
func _process_jobs():
|
||||
await get_tree().process_frame
|
||||
while _is_running:
|
||||
if _gedis_queue.is_paused(_queue_name):
|
||||
await get_tree().create_timer(1.0).timeout
|
||||
continue
|
||||
|
||||
var job_ids = []
|
||||
for i in range(batch_size):
|
||||
var job_id = _gedis.lpop(_gedis_queue._get_queue_key(_queue_name, GedisQueue.STATUS_WAITING))
|
||||
if not job_id:
|
||||
break
|
||||
job_ids.append(job_id)
|
||||
|
||||
if job_ids.is_empty():
|
||||
if Engine.is_editor_hint():
|
||||
await get_tree().process_frame
|
||||
else:
|
||||
if get_tree():
|
||||
await get_tree().process_frame
|
||||
continue
|
||||
|
||||
for job_id in job_ids:
|
||||
_gedis.lpush(_gedis_queue._get_queue_key(_queue_name, GedisQueue.STATUS_ACTIVE), job_id)
|
||||
var job = _gedis_queue.get_job(_queue_name, job_id)
|
||||
if not job:
|
||||
push_warning("GedisQueueWorker: Job with id %s not found." % job_id)
|
||||
continue
|
||||
|
||||
_jobs_in_progress[job_id] = job
|
||||
var job_key = _gedis_queue._get_job_key(_queue_name, job_id)
|
||||
_gedis.hset(job_key, "status", GedisQueue.STATUS_ACTIVE)
|
||||
_gedis_queue._gedis.publish(_gedis_queue._get_event_channel(_queue_name, "active"), {"job_id": job.id})
|
||||
_process_job(job)
|
||||
|
||||
if not _jobs_in_progress.is_empty():
|
||||
await _batch_completed
|
||||
|
||||
func _process_job(job: GedisJob):
|
||||
var result = await _processor.call(job)
|
||||
if result is Object and result.has_method("is_valid"):
|
||||
result = await result
|
||||
|
||||
if job.status == GedisQueue.STATUS_ACTIVE:
|
||||
job.complete(result)
|
||||
@ -1 +0,0 @@
|
||||
uid://ccq10gtvm32oo
|
||||
@ -1,67 +0,0 @@
|
||||
@tool
|
||||
extends VBoxContainer
|
||||
|
||||
# UI references will be set from the scene
|
||||
@export var queues_tree: Tree
|
||||
@export var jobs_tree: Tree
|
||||
@export var job_details_text: TextEdit
|
||||
@export var refresh_button: Button
|
||||
|
||||
var plugin
|
||||
|
||||
func _ready() -> void:
|
||||
if refresh_button:
|
||||
refresh_button.pressed.connect(_on_refresh_pressed)
|
||||
|
||||
# Set column titles for the queues tree
|
||||
queues_tree.set_column_titles_visible(true)
|
||||
queues_tree.set_column_title(0, "Queue")
|
||||
queues_tree.set_column_title(1, "Jobs")
|
||||
|
||||
# Set column titles for the jobs tree
|
||||
jobs_tree.set_column_titles_visible(true)
|
||||
jobs_tree.set_column_title(0, "ID")
|
||||
jobs_tree.set_column_title(1, "Queue")
|
||||
jobs_tree.set_column_title(2, "Status")
|
||||
|
||||
func set_plugin(p) -> void:
|
||||
plugin = p
|
||||
|
||||
func _on_refresh_pressed() -> void:
|
||||
if plugin:
|
||||
# This assumes the main dashboard has an instance selector
|
||||
var selected_id = get_parent().get_parent().instance_selector.get_item_id(get_parent().get_parent().instance_selector.selected)
|
||||
var session_id = plugin.get_current_session_id()
|
||||
if session_id != -1:
|
||||
plugin._fetch_keys_for_selected_instance(session_id)
|
||||
|
||||
func update_queues(data: Dictionary) -> void:
|
||||
queues_tree.clear()
|
||||
var root = queues_tree.create_item()
|
||||
queues_tree.hide_root = true
|
||||
|
||||
for queue_name in data:
|
||||
var queue_item = queues_tree.create_item(root)
|
||||
queue_item.set_text(0, queue_name)
|
||||
queue_item.set_text(1, str(data[queue_name].size()))
|
||||
|
||||
func update_jobs(jobs: Array) -> void:
|
||||
jobs_tree.clear()
|
||||
var root = jobs_tree.create_item()
|
||||
jobs_tree.hide_root = true
|
||||
|
||||
for job in jobs:
|
||||
var job_item = jobs_tree.create_item(root)
|
||||
job_item.set_text(0, job.id)
|
||||
job_item.set_text(1, job.queue_name)
|
||||
job_item.set_text(2, job.status)
|
||||
job_item.set_meta("data", job.data)
|
||||
|
||||
func _on_jobs_tree_item_selected() -> void:
|
||||
var selected_item = jobs_tree.get_selected()
|
||||
if selected_item:
|
||||
job_details_text.text = selected_item.get_meta("data")
|
||||
|
||||
func _on_queues_tree_item_selected() -> void:
|
||||
# TODO: should filter out jobs not the selected queue
|
||||
pass
|
||||
@ -1 +0,0 @@
|
||||
uid://c5fv5yw1cok24
|
||||
@ -1,48 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b5h5h5h5h5h5"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c5fv5yw1cok24" path="res://addons/GedisQueue/debugger/gedis_queue_debugger_panel.gd" id="1_abcde"]
|
||||
|
||||
[node name="GedisQueueDebuggerPanel" type="VBoxContainer" node_paths=PackedStringArray("queues_tree", "jobs_tree", "job_details_text", "refresh_button")]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("1_abcde")
|
||||
queues_tree = NodePath("HSplitContainer/QueuesTree")
|
||||
jobs_tree = NodePath("HSplitContainer/JobsVBox/JobsTree")
|
||||
job_details_text = NodePath("HSplitContainer/JobsVBox/JobDetailsText")
|
||||
refresh_button = NodePath("RefreshButton")
|
||||
|
||||
[node name="RefreshButton" type="Button" parent="."]
|
||||
layout_mode = 2
|
||||
text = "Refresh Queues"
|
||||
|
||||
[node name="HSplitContainer" type="HSplitContainer" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="QueuesTree" type="Tree" parent="HSplitContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 0.3
|
||||
columns = 2
|
||||
column_titles_visible = true
|
||||
select_mode = 1
|
||||
|
||||
[node name="JobsVBox" type="VBoxContainer" parent="HSplitContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="JobsTree" type="Tree" parent="HSplitContainer/JobsVBox"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
columns = 3
|
||||
column_titles_visible = true
|
||||
select_mode = 1
|
||||
|
||||
[node name="JobDetailsText" type="TextEdit" parent="HSplitContainer/JobsVBox"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
editable = false
|
||||
|
||||
[connection signal="item_selected" from="HSplitContainer/QueuesTree" to="." method="_on_queues_tree_item_selected"]
|
||||
[connection signal="item_selected" from="HSplitContainer/JobsVBox/JobsTree" to="." method="_on_jobs_tree_item_selected"]
|
||||
|
Before Width: | Height: | Size: 103 KiB |
@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://48jxifffcg1y"
|
||||
path="res://.godot/imported/icon.png-bff46c8d6bc629df6e2505dfe04b11b4.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/GedisQueue/icon.png"
|
||||
dest_files=["res://.godot/imported/icon.png-bff46c8d6bc629df6e2505dfe04b11b4.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
@ -1,7 +0,0 @@
|
||||
[plugin]
|
||||
|
||||
name="GedisQueue"
|
||||
description="A BullMQ-like queue system for Godot, backed by Gedis."
|
||||
author="NodotProject"
|
||||
version="0.1.9"
|
||||
script="plugin.gd"
|
||||
@ -1,87 +0,0 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const GedisQueueDebuggerPanel = preload("res://addons/GedisQueue/debugger/gedis_queue_debugger_panel.tscn")
|
||||
|
||||
var queue_debugger_plugin
|
||||
var queue_panel
|
||||
var dashboard
|
||||
|
||||
func _enter_tree():
|
||||
queue_debugger_plugin = GedisQueueDebuggerPlugin.new()
|
||||
add_debugger_plugin(queue_debugger_plugin)
|
||||
|
||||
var timer = Timer.new()
|
||||
timer.wait_time = 1
|
||||
timer.timeout.connect(_on_timer_timeout)
|
||||
add_child(timer)
|
||||
timer.start()
|
||||
|
||||
func _on_timer_timeout():
|
||||
var editor_node = EditorInterface.get_base_control()
|
||||
dashboard = editor_node.find_child("Gedis", true, false)
|
||||
|
||||
if dashboard:
|
||||
var debugger = dashboard.plugin
|
||||
if debugger:
|
||||
var tab_container = dashboard.find_child("TabContainer", true, false)
|
||||
if tab_container:
|
||||
queue_panel = dashboard.find_child("Queue", true, false)
|
||||
if !queue_panel:
|
||||
queue_panel = GedisQueueDebuggerPanel.instantiate()
|
||||
queue_panel.name = "Queue"
|
||||
tab_container.add_child(queue_panel)
|
||||
queue_panel.set_plugin(debugger)
|
||||
|
||||
var session_id = debugger.get_current_session_id()
|
||||
if session_id != -1:
|
||||
queue_debugger_plugin.set_queue_panel(session_id, queue_panel)
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
if queue_debugger_plugin:
|
||||
remove_debugger_plugin(queue_debugger_plugin)
|
||||
queue_debugger_plugin = null
|
||||
if queue_panel:
|
||||
queue_panel.queue_free()
|
||||
|
||||
class GedisQueueDebuggerPlugin extends EditorDebuggerPlugin:
|
||||
var queue_panels = {}
|
||||
|
||||
func set_queue_panel(session_id, panel):
|
||||
queue_panels[session_id] = panel
|
||||
|
||||
func _has_capture(capture):
|
||||
return capture == "gedis"
|
||||
|
||||
func _capture(message, data, session_id):
|
||||
var parts = message.split(":")
|
||||
var kind = parts[1] if parts.size() > 1 else ""
|
||||
|
||||
if session_id in queue_panels:
|
||||
var queue_panel = queue_panels[session_id]
|
||||
match kind:
|
||||
"snapshot_data":
|
||||
if queue_panel:
|
||||
var snapshot = data[0]
|
||||
var queues = {}
|
||||
var jobs = []
|
||||
|
||||
for key in snapshot:
|
||||
var value = snapshot[key]
|
||||
if "job" in key:
|
||||
var job_data = value["value"]
|
||||
var key_parts = key.split(":")
|
||||
if key_parts.size() > 2:
|
||||
job_data["queue_name"] = key_parts[1]
|
||||
jobs.append(job_data)
|
||||
else:
|
||||
var queue_name = key.replace("gedis_queue:", "").replace(":waiting", "").replace(":active", "")
|
||||
if not queue_name in queues:
|
||||
queues[queue_name] = []
|
||||
queues[queue_name].append_array(value["value"])
|
||||
|
||||
queue_panel.update_queues(queues)
|
||||
queue_panel.update_jobs(jobs)
|
||||
return true
|
||||
return false
|
||||
@ -1 +0,0 @@
|
||||
uid://vbxscf2mqmmy
|
||||
|
Before Width: | Height: | Size: 115 B |
@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cim3uuxhxs3x4"
|
||||
path="res://.godot/imported/Blank.png-e20d4d6a47b08c47420d456c717ca61f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PaletteTools/Images/Blank.png"
|
||||
dest_files=["res://.godot/imported/Blank.png-e20d4d6a47b08c47420d456c717ca61f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
|
Before Width: | Height: | Size: 146 B |
@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bv0rua34hrcsa"
|
||||
path="res://.godot/imported/Plus.png-e4e5eebf42f899e6f55e4d0ffe60c50a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PaletteTools/Images/Plus.png"
|
||||
dest_files=["res://.godot/imported/Plus.png-e4e5eebf42f899e6f55e4d0ffe60c50a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
|
Before Width: | Height: | Size: 4.6 KiB |
@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b6f62u2b00rdv"
|
||||
path="res://.godot/imported/checked.png-d81f80362f72567a0fcdbf6bf355283e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PaletteTools/Images/checked.png"
|
||||
dest_files=["res://.godot/imported/checked.png-d81f80362f72567a0fcdbf6bf355283e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
|
Before Width: | Height: | Size: 5.3 KiB |
@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://blxftvivd7x5x"
|
||||
path="res://.godot/imported/checked_4k.png-342c0f16a75c36f211dc8d6291dcbfde.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PaletteTools/Images/checked_4k.png"
|
||||
dest_files=["res://.godot/imported/checked_4k.png-342c0f16a75c36f211dc8d6291dcbfde.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
|
Before Width: | Height: | Size: 170 B |
@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://duuedcimehb1f"
|
||||
path="res://.godot/imported/spyglass.png-c8a5839ba62e6621291233abb45e1ee9.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PaletteTools/Images/spyglass.png"
|
||||
dest_files=["res://.godot/imported/spyglass.png-c8a5839ba62e6621291233abb45e1ee9.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
|
Before Width: | Height: | Size: 4.3 KiB |
@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://585mb8p87t5x"
|
||||
path="res://.godot/imported/unchecked.png-5249d27fb8a908d32590159e278c99c8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PaletteTools/Images/unchecked.png"
|
||||
dest_files=["res://.godot/imported/unchecked.png-5249d27fb8a908d32590159e278c99c8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
|
Before Width: | Height: | Size: 4.4 KiB |
@ -1,40 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bpiotwvx0p7kp"
|
||||
path="res://.godot/imported/unchecked_4k.png-4470f9e94d7b68e36513e292443cc559.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PaletteTools/Images/unchecked_4k.png"
|
||||
dest_files=["res://.godot/imported/unchecked_4k.png-4470f9e94d7b68e36513e292443cc559.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
@ -1,19 +0,0 @@
|
||||
Copyright (c) 2023 RancidMilk
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -1,63 +0,0 @@
|
||||
# Godot 4 Palette Tools
|
||||
|
||||
This is a small addon that allows for easy color palette integration in Godot 4. Quickly Create/Download palettes. Load/Save for use any time. Comes with the ability to quickly switch the editor's color-picker swatches as well as a custom color picker that can switch between palettes on demand. Download option currently supports Lospec palettes.
|
||||
|
||||
## Setup
|
||||
|
||||
### Install Methods
|
||||
|
||||
#### Recommended:
|
||||
|
||||
Open the *AssetLib* tab in the editor and search for Palette Tools.
|
||||
|
||||
#### Manual Method:
|
||||
|
||||
From either the [project repo](https://github.com/RancidMilkGames/GodotPaletteTools) or [asset library website](https://godotengine.org/asset-library/asset) copy the "PaletteTools" folder to your project's "addons" folder. If this is your first time using a Godot plugin/addon, and you don't have an addons folder, make a folder named "addons" in the root of your project.
|
||||
|
||||
### Activate
|
||||
|
||||
Godot will automatically activate the addon if you used the previously mentioned recommended method. If done manually, go to `Project Settings->Plugins` to activate it.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td align="center">
|
||||
:warning: Notice
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Plugin will not be immediately noticeable. It will appear in the right-side dock with Inspector/Node/History. You may need to either expand the dock or use the arrows to access it.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## USE
|
||||
|
||||
* **Custom Palette Picker**: Replaces the default color picker with an extended one that can switch between palettes. For special use cases and not thoroughly tested so by default it's set to off.
|
||||
* **Saved Palettes**: List of previously saved palettes. To load or delete a palette, select it in the list and then press the corresponding button.
|
||||
* **Get from Lospec**: Either enter a Lospec palette URL or browse through popular palettes. The plugin will automatically keep track of the palette name, and it's author for referencing or crediting.
|
||||
* **Palette Preview**: Preview/editor for palettes. Click the plus sign to add a color to the palette. Existing colors can be edited by clicking on them, or removed with the red close button in the top right corner of the button. The save palette button will store the palette.
|
||||
* **Replace Editor Swatches**: If you have an eligible palette, an option will appear that allows you to save it to the editor's swatches, which can be accessed from both the default color picker and the extended one.
|
||||
|
||||
## Tips
|
||||
|
||||
* You can copy the "res://addons/PaletteTools/color_presets.cfg" file to move saved palettes/settings between projects
|
||||
|
||||
## Future
|
||||
|
||||
I consider this plugin mostly done, but still use it regularly, so if Godot has any breaking changes that affect it, those will probably be addressed. It also might get the occasional new feature. Some current thoughts are:
|
||||
|
||||
- [ ] Theme integration of some sort
|
||||
- [ ] The ability to mass convert one color in the project to another
|
||||
|
||||
## Notes
|
||||
|
||||
* Tested on Godot 4.2. If you'd like to run it on an earlier version of Godot 4, [this is a link](https://github.com/RancidMilkGames/GodotPaletteTools/tree/47c09b8d6e43a0acc0380a1344a4b2282f95d49b) to the last commit before the 4.2 changes.
|
||||
* Custom Color picker won't work when editing resources. I'm not sure if this is possible to implement in Godot's current state.
|
||||
@ -1,9 +0,0 @@
|
||||
[gd_resource type="LabelSettings" format=3 uid="uid://d21bg0y0svxi3"]
|
||||
|
||||
[resource]
|
||||
font_size = 24
|
||||
outline_size = 4
|
||||
outline_color = Color(0, 0, 0, 1)
|
||||
shadow_size = 6
|
||||
shadow_color = Color(0.714494, 0.571648, 0.404247, 1)
|
||||
shadow_offset = Vector2(0, 0)
|
||||
@ -1,8 +0,0 @@
|
||||
[gd_resource type="LabelSettings" format=3 uid="uid://k60qt3dd7ov2"]
|
||||
|
||||
[resource]
|
||||
font_size = 50
|
||||
font_color = Color(0.545098, 0.333333, 0.501961, 1)
|
||||
outline_color = Color(0.772549, 0.8, 0.721569, 1)
|
||||
shadow_color = Color(0.760784, 0.552941, 0.458824, 1)
|
||||
shadow_offset = Vector2(0, 0)
|
||||
@ -1,83 +0,0 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://xj8jb4l74mf"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/browse_palette_preview.gd" id="1_j460a"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g4ruc"]
|
||||
content_margin_left = 25.0
|
||||
content_margin_top = 25.0
|
||||
content_margin_right = 25.0
|
||||
content_margin_bottom = 25.0
|
||||
bg_color = Color(0.435294, 0.403922, 0.462745, 1)
|
||||
border_width_left = 10
|
||||
border_width_top = 10
|
||||
border_width_right = 10
|
||||
border_width_bottom = 10
|
||||
border_color = Color(0, 0, 0, 1)
|
||||
border_blend = true
|
||||
corner_radius_top_left = 25
|
||||
corner_radius_top_right = 25
|
||||
corner_radius_bottom_right = 25
|
||||
corner_radius_bottom_left = 25
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qogr7"]
|
||||
content_margin_left = 7.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 7.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.713726, 0.572549, 0.403922, 1)
|
||||
border_width_left = 5
|
||||
border_width_top = 5
|
||||
border_width_right = 5
|
||||
border_width_bottom = 5
|
||||
border_color = Color(0.00820202, 0.00820203, 0.00820202, 1)
|
||||
border_blend = true
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_nkmub"]
|
||||
font_size = 24
|
||||
font_color = Color(0, 0, 0, 1)
|
||||
outline_size = 4
|
||||
outline_color = Color(0.552941, 0.384314, 0.407843, 1)
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" node_paths=PackedStringArray("info_label", "color_container")]
|
||||
offset_top = 54.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 54.0
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_g4ruc")
|
||||
script = ExtResource("1_j460a")
|
||||
info_label = NodePath("BrowsePalettePreview/HBoxContainer/Label")
|
||||
color_container = NodePath("BrowsePalettePreview/MarginContainer/HFlowContainer")
|
||||
|
||||
[node name="BrowsePalettePreview" type="VBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="BrowsePalettePreview"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PaletteSelect" type="Button" parent="BrowsePalettePreview/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_qogr7")
|
||||
text = "Import"
|
||||
|
||||
[node name="Label" type="Label" parent="BrowsePalettePreview/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Palette name by user"
|
||||
label_settings = SubResource("LabelSettings_nkmub")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="BrowsePalettePreview"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HFlowContainer" type="HFlowContainer" parent="BrowsePalettePreview/MarginContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/h_separation = 0
|
||||
theme_override_constants/v_separation = 0
|
||||
|
||||
[connection signal="pressed" from="BrowsePalettePreview/HBoxContainer/PaletteSelect" to="." method="_on_palette_select_pressed"]
|
||||
@ -1,70 +0,0 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://157npdwcov77"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/color_sample.gd" id="1_8rxjv"]
|
||||
[ext_resource type="Texture2D" path="res://addons/PaletteTools/Images/Blank.png" id="1_jr35n"]
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_s1xg4"]
|
||||
font_size = 20
|
||||
font_color = Color(0.113725, 0.113725, 0.113725, 1)
|
||||
|
||||
[node name="ColorSample" type="MarginContainer" node_paths=PackedStringArray("color_picker_button", "remove_button", "remove_button_text")]
|
||||
custom_minimum_size = Vector2(50, 50)
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -12.5
|
||||
offset_top = -12.5
|
||||
offset_right = 12.5
|
||||
offset_bottom = 12.5
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/margin_left = 5
|
||||
theme_override_constants/margin_top = 5
|
||||
theme_override_constants/margin_right = 5
|
||||
theme_override_constants/margin_bottom = 5
|
||||
script = ExtResource("1_8rxjv")
|
||||
color_picker_button = NodePath("Color")
|
||||
remove_button = NodePath("Color/Remove")
|
||||
remove_button_text = NodePath("Color/Remove/Label")
|
||||
|
||||
[node name="Color" type="ColorPickerButton" parent="."]
|
||||
custom_minimum_size = Vector2(50, 50)
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
|
||||
[node name="Remove" type="TextureButton" parent="Color"]
|
||||
self_modulate = Color(0.980392, 0.513726, 0.47451, 1)
|
||||
custom_minimum_size = Vector2(10, 10)
|
||||
layout_mode = 1
|
||||
anchors_preset = 1
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
offset_left = -14.0
|
||||
offset_top = 4.0
|
||||
offset_right = -4.0
|
||||
offset_bottom = 14.0
|
||||
grow_horizontal = 0
|
||||
mouse_default_cursor_shape = 2
|
||||
texture_normal = ExtResource("1_jr35n")
|
||||
ignore_texture_size = true
|
||||
stretch_mode = 4
|
||||
|
||||
[node name="Label" type="Label" parent="Color/Remove"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -20.0
|
||||
offset_top = -13.0
|
||||
offset_right = 20.0
|
||||
offset_bottom = 13.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "X"
|
||||
label_settings = SubResource("LabelSettings_s1xg4")
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
@ -1,20 +0,0 @@
|
||||
[gd_scene format=3 uid="uid://c41c3pd6clxuu"]
|
||||
|
||||
[node name="CustomPalette" type="ColorRect"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
color = Color(1, 1, 1, 0.996078)
|
||||
|
||||
[node name="Button" type="Button" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
flat = true
|
||||
@ -1,140 +0,0 @@
|
||||
[gd_scene load_steps=10 format=3 uid="uid://te84lilqmwp0"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/custom_picker.gd" id="1_oxy04"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_igykn"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.745098, 0.584314, 0.360784, 1)
|
||||
corner_radius_top_left = 10
|
||||
corner_radius_top_right = 10
|
||||
corner_radius_bottom_right = 10
|
||||
corner_radius_bottom_left = 10
|
||||
corner_detail = 6
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_etl6w"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.603922, 0.309804, 0.313726, 1)
|
||||
corner_radius_top_left = 10
|
||||
corner_radius_top_right = 10
|
||||
corner_radius_bottom_right = 10
|
||||
corner_radius_bottom_left = 10
|
||||
corner_detail = 6
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jt85f"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.772549, 0.8, 0.721569, 1)
|
||||
corner_radius_top_left = 10
|
||||
corner_radius_top_right = 10
|
||||
corner_radius_bottom_right = 10
|
||||
corner_radius_bottom_left = 10
|
||||
corner_detail = 6
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1lt0k"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 0.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 0.0
|
||||
bg_color = Color(0.407843, 0.67451, 0.662745, 1)
|
||||
corner_radius_top_left = 10
|
||||
corner_radius_top_right = 10
|
||||
corner_radius_bottom_right = 10
|
||||
corner_radius_bottom_left = 10
|
||||
corner_detail = 6
|
||||
|
||||
[sub_resource type="Theme" id="Theme_8u2vs"]
|
||||
VScrollBar/styles/grabber = SubResource("StyleBoxFlat_igykn")
|
||||
VScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_etl6w")
|
||||
VScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_jt85f")
|
||||
VScrollBar/styles/scroll = SubResource("StyleBoxFlat_1lt0k")
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_jcc3n"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_t77tn"]
|
||||
bg_color = Color(0.839216, 0.839216, 0.839216, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ykbik"]
|
||||
bg_color = Color(0.839216, 0.839216, 0.839216, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[node name="CustomPicker" type="PopupPanel" node_paths=PackedStringArray("color_picker", "saved_palettes", "apply_palette_button")]
|
||||
disable_3d = true
|
||||
title = "Palette Tools Color-Picker"
|
||||
size = Vector2i(1280, 2700)
|
||||
visible = true
|
||||
min_size = Vector2i(640, 1350)
|
||||
theme = SubResource("Theme_8u2vs")
|
||||
script = ExtResource("1_oxy04")
|
||||
color_picker = NodePath("ScrollContainer/VBoxContainer/ColorPicker")
|
||||
saved_palettes = NodePath("ScrollContainer/VBoxContainer/SavedPalettes")
|
||||
apply_palette_button = NodePath("ScrollContainer/VBoxContainer/HBoxContainer/Button")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 4.0
|
||||
offset_top = 4.0
|
||||
offset_right = -4.0
|
||||
offset_bottom = -4.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer"]
|
||||
custom_minimum_size = Vector2(300, 600)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
alignment = 1
|
||||
|
||||
[node name="ColorPicker" type="ColorPicker" parent="ScrollContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
deferred_mode = true
|
||||
picker_shape = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="ScrollContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="ScrollContainer/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Saved Palettes:
|
||||
"
|
||||
|
||||
[node name="Spacer" type="Control" parent="ScrollContainer/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Button" type="Button" parent="ScrollContainer/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Applies palette to Swatches. Alternatively you can double click a palette or press enter with a palette highlighted"
|
||||
text = "Apply to Swatches"
|
||||
|
||||
[node name="SavedPalettes" type="ItemList" parent="ScrollContainer/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(300, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
tooltip_text = "Double click on palette or select one and hit enter to apply it."
|
||||
theme_override_colors/font_selected_color = Color(0.0862745, 0.0862745, 0.0862745, 1)
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_jcc3n")
|
||||
theme_override_styles/selected = SubResource("StyleBoxFlat_t77tn")
|
||||
theme_override_styles/selected_focus = SubResource("StyleBoxFlat_ykbik")
|
||||
auto_height = true
|
||||
item_count = 1
|
||||
same_column_width = true
|
||||
item_0/text = "No Palettes"
|
||||
@ -1,647 +0,0 @@
|
||||
[gd_scene load_steps=42 format=3 uid="uid://3m5qn5hayqy3"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/palette_tool.gd" id="1_coeap"]
|
||||
[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/get_from_url.gd" id="2_4p84m"]
|
||||
[ext_resource type="PackedScene" uid="uid://x40sgfxkrskd" path="res://addons/PaletteTools/Scenes/plus_box.tscn" id="2_85d0j"]
|
||||
[ext_resource type="PackedScene" uid="uid://157npdwcov77" path="res://addons/PaletteTools/Scenes/color_sample.tscn" id="2_fn217"]
|
||||
[ext_resource type="Shader" path="res://addons/PaletteTools/Shaders/rainbow_shader.gdshader" id="3_gaxkd"]
|
||||
[ext_resource type="Texture2D" path="res://addons/PaletteTools/Images/Plus.png" id="3_m16i6"]
|
||||
[ext_resource type="Texture2D" path="res://addons/PaletteTools/Images/spyglass.png" id="4_7wo4d"]
|
||||
[ext_resource type="LabelSettings" uid="uid://k60qt3dd7ov2" path="res://addons/PaletteTools/Resources/section_label_settings_contrast.tres" id="4_qjnhp"]
|
||||
[ext_resource type="Texture2D" path="res://addons/PaletteTools/Images/checked_4k.png" id="6_iloov"]
|
||||
[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/browse_popup.gd" id="7_ob3x8"]
|
||||
[ext_resource type="Texture2D" path="res://addons/PaletteTools/Images/unchecked_4k.png" id="7_rs6oc"]
|
||||
[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/loading_palettes_screen.gd" id="9_xvc3k"]
|
||||
[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/alert_popup_panel.gd" id="11_hklnm"]
|
||||
[ext_resource type="PackedScene" uid="uid://xj8jb4l74mf" path="res://addons/PaletteTools/Scenes/browse_palette_preview.tscn" id="13_a5syi"]
|
||||
|
||||
[sub_resource type="Theme" id="Theme_igl82"]
|
||||
MarginContainer/constants/margin_bottom = 3
|
||||
MarginContainer/constants/margin_left = 3
|
||||
MarginContainer/constants/margin_right = 3
|
||||
MarginContainer/constants/margin_top = 3
|
||||
TextEdit/font_sizes/font_size = 16
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7qvmi"]
|
||||
bg_color = Color(0.713726, 0.572549, 0.403922, 1)
|
||||
|
||||
[sub_resource type="Theme" id="Theme_8bgoc"]
|
||||
PanelContainer/styles/panel = SubResource("StyleBoxFlat_7qvmi")
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_g1sn2"]
|
||||
shader = ExtResource("3_gaxkd")
|
||||
shader_parameter/strength = 0.25
|
||||
shader_parameter/speed = 0.25
|
||||
shader_parameter/angle = 0.0
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_yeikm"]
|
||||
font_size = 80
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_u67lc"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1qaos"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kl4yb"]
|
||||
bg_color = Color(0.839216, 0.839216, 0.839216, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2u8j5"]
|
||||
bg_color = Color(0.839216, 0.839216, 0.839216, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g30ga"]
|
||||
bg_color = Color(0.929412, 0.882353, 0.619608, 0.823529)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c6ft6"]
|
||||
bg_color = Color(0.92549, 0.85098, 0.596078, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_b113t"]
|
||||
bg_color = Color(0.415686, 0.415686, 0.415686, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_kk1ek"]
|
||||
|
||||
[sub_resource type="Theme" id="Theme_t5g4x"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4b8x1"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.890196, 0.890196, 0.890196, 0.6)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
corner_detail = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_inmej"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.1, 0.1, 0.1, 0.6)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
corner_detail = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_u8dak"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.0235294, 0.0235294, 0.0235294, 0.6)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
corner_detail = 5
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_rgr51"]
|
||||
font_size = 40
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_jnb0d"]
|
||||
font_size = 40
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6d60a"]
|
||||
content_margin_left = 5.0
|
||||
content_margin_right = 5.0
|
||||
bg_color = Color(0.890196, 0.337255, 0.341176, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y2upq"]
|
||||
bg_color = Color(0.768627, 0.764706, 0.509804, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.121569, 0.121569, 0.121569, 1)
|
||||
corner_radius_top_left = 10
|
||||
corner_radius_top_right = 10
|
||||
corner_radius_bottom_right = 10
|
||||
corner_radius_bottom_left = 10
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7ta0s"]
|
||||
bg_color = Color(0.25098, 0.25098, 0.25098, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.933333, 0.772549, 0.509804, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ceb4a"]
|
||||
bg_color = Color(0.333333, 0.333333, 0.333333, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_ww1rl"]
|
||||
font_size = 40
|
||||
font_color = Color(0.0823529, 0.0823529, 0.0823529, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tdaa4"]
|
||||
bg_color = Color(0.172549, 0.172549, 0.172549, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_0flab"]
|
||||
font_size = 32
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_26hsa"]
|
||||
font_size = 100
|
||||
font_color = Color(0.00820202, 0.00820203, 0.00820202, 1)
|
||||
|
||||
[node name="Palette Tools" type="ScrollContainer"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Colors" type="PanelContainer" parent="." node_paths=PackedStringArray("http", "url", "color_preview", "editor_swatch_save", "restart_editor", "alert", "p_name_text", "p_author_text", "saved_palettes", "clear_preview_button", "col_pick_popup", "browse_popup_panel", "custom_picker_check_box")]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme = SubResource("Theme_igl82")
|
||||
script = ExtResource("1_coeap")
|
||||
http = NodePath("HTTPRequest")
|
||||
url = NodePath("Palette/Search/LineEdit")
|
||||
color_preview = NodePath("Palette/ColorPreview")
|
||||
editor_swatch_save = NodePath("Palette/Container/VBoxContainer/SaveToEditor")
|
||||
restart_editor = NodePath("Palette/Container/VBoxContainer/RestartEditor")
|
||||
alert = NodePath("AlertPopup")
|
||||
p_name_text = NodePath("Palette/Info/PaletteName/LineEdit")
|
||||
p_author_text = NodePath("Palette/Info/Author/LineEdit")
|
||||
saved_palettes = NodePath("Palette/SavedPalettes")
|
||||
clear_preview_button = NodePath("Palette/HBoxContainer/Clear")
|
||||
col_pick_popup = NodePath("ColorPickerPopup")
|
||||
browse_popup_panel = NodePath("BrowsePopup")
|
||||
add_color_scene = ExtResource("2_85d0j")
|
||||
color_sample = ExtResource("2_fn217")
|
||||
custom_picker_check_box = NodePath("Palette/CustomPicker/CustomPickerCheckBox")
|
||||
|
||||
[node name="HTTPRequest" type="HTTPRequest" parent="Colors" node_paths=PackedStringArray("colors")]
|
||||
download_file = "res://addons/PaletteTools/temp_palette.txt"
|
||||
script = ExtResource("2_4p84m")
|
||||
colors = NodePath("..")
|
||||
|
||||
[node name="Palette" type="VBoxContainer" parent="Colors"]
|
||||
layout_mode = 2
|
||||
theme = SubResource("Theme_8bgoc")
|
||||
theme_override_constants/separation = 5
|
||||
alignment = 1
|
||||
|
||||
[node name="Title" type="Label" parent="Colors/Palette"]
|
||||
material = SubResource("ShaderMaterial_g1sn2")
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
text = "Palette Tools"
|
||||
label_settings = SubResource("LabelSettings_yeikm")
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="CustomPicker" type="HBoxContainer" parent="Colors/Palette"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="CustomPickerCheckBox" type="CheckBox" parent="Colors/Palette/CustomPicker"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Toggle an extended color picker that can switch palettes"
|
||||
theme_override_font_sizes/font_size = 30
|
||||
theme_override_icons/checked = ExtResource("6_iloov")
|
||||
theme_override_icons/unchecked = ExtResource("7_rs6oc")
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
|
||||
[node name="RichTextLabel" type="RichTextLabel" parent="Colors/Palette/CustomPicker"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
theme_override_font_sizes/normal_font_size = 40
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_u67lc")
|
||||
bbcode_enabled = true
|
||||
text = "Custom Palette Picker [color=9a4f50](beta)[/color]"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
autowrap_mode = 0
|
||||
|
||||
[node name="PaletteListTitle" type="Label" parent="Colors/Palette"]
|
||||
layout_mode = 2
|
||||
text = "--- Saved Palettes: ---"
|
||||
label_settings = ExtResource("4_qjnhp")
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="SavedPalettes" type="ItemList" parent="Colors/Palette"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_selected_color = Color(0.0862745, 0.0862745, 0.0862745, 1)
|
||||
theme_override_font_sizes/font_size = 30
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_1qaos")
|
||||
theme_override_styles/selected = SubResource("StyleBoxFlat_kl4yb")
|
||||
theme_override_styles/selected_focus = SubResource("StyleBoxFlat_2u8j5")
|
||||
auto_height = true
|
||||
item_count = 1
|
||||
same_column_width = true
|
||||
item_0/text = "No Palettes"
|
||||
|
||||
[node name="IO" type="HBoxContainer" parent="Colors/Palette"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="New" type="TextureButton" parent="Colors/Palette/IO"]
|
||||
visible = false
|
||||
self_modulate = Color(0.720467, 0.720468, 0.720467, 1)
|
||||
texture_filter = 1
|
||||
custom_minimum_size = Vector2(80, 0)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Make a new palette. Erases current preview"
|
||||
mouse_default_cursor_shape = 2
|
||||
texture_normal = ExtResource("3_m16i6")
|
||||
texture_pressed = ExtResource("3_m16i6")
|
||||
texture_hover = ExtResource("3_m16i6")
|
||||
ignore_texture_size = true
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="Load" type="Button" parent="Colors/Palette/IO"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
tooltip_text = "Load selected Saved Palette"
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_override_font_sizes/font_size = 30
|
||||
text = "Load"
|
||||
|
||||
[node name="Delete" type="Button" parent="Colors/Palette/IO"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
tooltip_text = "Delete selected Saved Palette"
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_override_font_sizes/font_size = 30
|
||||
text = "Delete"
|
||||
|
||||
[node name="SearchTitle" type="Label" parent="Colors/Palette"]
|
||||
layout_mode = 2
|
||||
text = "--- Get from Lospec: ---"
|
||||
label_settings = ExtResource("4_qjnhp")
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Search" type="HBoxContainer" parent="Colors/Palette"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Colors/Palette/Search"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_font_sizes/font_size = 30
|
||||
placeholder_text = "Lospec Palette URL"
|
||||
expand_to_text_length = true
|
||||
caret_blink = true
|
||||
caret_blink_interval = 0.5
|
||||
|
||||
[node name="Search" type="Button" parent="Colors/Palette/Search"]
|
||||
texture_filter = 1
|
||||
custom_minimum_size = Vector2(80, 4)
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_override_font_sizes/font_size = 40
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_g30ga")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_c6ft6")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_b113t")
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_kk1ek")
|
||||
icon = ExtResource("4_7wo4d")
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
|
||||
[node name="BrowsePalettesButton" type="Button" parent="Colors/Palette"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Browse the most popular Lospec palettes"
|
||||
mouse_default_cursor_shape = 2
|
||||
theme = SubResource("Theme_t5g4x")
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
theme_override_colors/font_pressed_color = Color(0.91664, 0.91664, 0.91664, 1)
|
||||
theme_override_colors/font_hover_color = Color(0.813258, 0.813258, 0.813258, 1)
|
||||
theme_override_font_sizes/font_size = 30
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_4b8x1")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_inmej")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_u8dak")
|
||||
text = "Browse Palettes"
|
||||
|
||||
[node name="PreviewTitle" type="Label" parent="Colors/Palette"]
|
||||
layout_mode = 2
|
||||
text = "--- Palette Preview: ---"
|
||||
label_settings = ExtResource("4_qjnhp")
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Info" type="VBoxContainer" parent="Colors/Palette"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="PaletteName" type="HBoxContainer" parent="Colors/Palette/Info"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Colors/Palette/Info/PaletteName"]
|
||||
layout_mode = 2
|
||||
text = "Palette Name:"
|
||||
label_settings = SubResource("LabelSettings_rgr51")
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Colors/Palette/Info/PaletteName"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_font_sizes/font_size = 30
|
||||
placeholder_text = "Name"
|
||||
caret_blink = true
|
||||
caret_blink_interval = 0.5
|
||||
|
||||
[node name="Author" type="HBoxContainer" parent="Colors/Palette/Info"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Colors/Palette/Info/Author"]
|
||||
layout_mode = 2
|
||||
text = "Author:"
|
||||
label_settings = SubResource("LabelSettings_jnb0d")
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Colors/Palette/Info/Author"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_font_sizes/font_size = 30
|
||||
placeholder_text = "Name"
|
||||
caret_blink = true
|
||||
caret_blink_interval = 0.5
|
||||
|
||||
[node name="ColorPreview" type="HFlowContainer" parent="Colors/Palette"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
alignment = 1
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Colors/Palette"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
alignment = 1
|
||||
|
||||
[node name="Save" type="Button" parent="Colors/Palette/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
tooltip_text = "Save the current preview to Saved Palettes"
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_override_font_sizes/font_size = 30
|
||||
text = "Save Palette"
|
||||
|
||||
[node name="Clear" type="Button" parent="Colors/Palette/HBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
tooltip_text = "Save the current preview to Saved Palettes"
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_override_colors/font_color = Color(0.125911, 0.125911, 0.125911, 1)
|
||||
theme_override_font_sizes/font_size = 30
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_6d60a")
|
||||
text = "Clear Preview"
|
||||
|
||||
[node name="Container" type="MarginContainer" parent="Colors/Palette"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ColorRect" type="Panel" parent="Colors/Palette/Container"]
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_y2upq")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Colors/Palette/Container"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SaveToEditor" type="VBoxContainer" parent="Colors/Palette/Container/VBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
alignment = 1
|
||||
|
||||
[node name="Spacer" type="Control" parent="Colors/Palette/Container/VBoxContainer/SaveToEditor"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SaveToEditorButton" type="Button" parent="Colors/Palette/Container/VBoxContainer/SaveToEditor"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
tooltip_text = "Replaces the current editor swatches with current palette"
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_override_colors/font_color = Color(0.933333, 0.772549, 0.509804, 1)
|
||||
theme_override_font_sizes/font_size = 30
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_7ta0s")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_ceb4a")
|
||||
text = "*Save to Editor's Swatches"
|
||||
|
||||
[node name="Label" type="Label" parent="Colors/Palette/Container/VBoxContainer/SaveToEditor"]
|
||||
layout_mode = 2
|
||||
text = "*Note: Editor must be restarted
|
||||
for changes to take effect"
|
||||
label_settings = SubResource("LabelSettings_ww1rl")
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="RestartEditor" type="VBoxContainer" parent="Colors/Palette/Container/VBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
alignment = 1
|
||||
|
||||
[node name="RestartEditorButton" type="Button" parent="Colors/Palette/Container/VBoxContainer/RestartEditor"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
tooltip_text = "Replaces the current editor swatches with current palette"
|
||||
theme_override_colors/font_color = Color(1, 0.435294, 0.364706, 1)
|
||||
theme_override_font_sizes/font_size = 30
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_tdaa4")
|
||||
text = "*Restart To finalize changes?"
|
||||
|
||||
[node name="Spacer" type="Control" parent="Colors/Palette/Container/VBoxContainer/RestartEditor"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="AlertPopup" type="PopupPanel" parent="Colors" node_paths=PackedStringArray("alert_text_label")]
|
||||
disable_3d = true
|
||||
title = "Alert"
|
||||
initial_position = 1
|
||||
size = Vector2i(500, 300)
|
||||
script = ExtResource("11_hklnm")
|
||||
alert_text_label = NodePath("Control/Label")
|
||||
|
||||
[node name="Control" type="Control" parent="Colors/AlertPopup"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 4.0
|
||||
offset_top = 4.0
|
||||
offset_right = -4.0
|
||||
offset_bottom = -4.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Colors/AlertPopup/Control"]
|
||||
custom_minimum_size = Vector2(400, 200)
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -0.5
|
||||
offset_top = -13.0
|
||||
offset_right = 0.5
|
||||
offset_bottom = 13.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "Alert"
|
||||
label_settings = SubResource("LabelSettings_0flab")
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="CloseButton" type="Button" parent="Colors/AlertPopup/Control"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -25.0
|
||||
offset_top = -42.0
|
||||
offset_right = 25.0
|
||||
offset_bottom = -11.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
size_flags_horizontal = 6
|
||||
size_flags_vertical = 6
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Close"
|
||||
|
||||
[node name="ColorPickerPopup" type="PopupPanel" parent="Colors"]
|
||||
disable_3d = true
|
||||
sdf_oversize = 0
|
||||
initial_position = 1
|
||||
size = Vector2i(306, 514)
|
||||
unresizable = false
|
||||
|
||||
[node name="ColorPicker" type="ColorPicker" parent="Colors/ColorPickerPopup"]
|
||||
offset_left = 4.0
|
||||
offset_top = 4.0
|
||||
offset_right = 302.0
|
||||
offset_bottom = 510.0
|
||||
picker_shape = 3
|
||||
can_add_swatches = false
|
||||
presets_visible = false
|
||||
|
||||
[node name="BrowsePopup" type="Window" parent="Colors" node_paths=PackedStringArray("colors", "browse_http", "preview_container", "alert_popup", "loading_screen")]
|
||||
disable_3d = true
|
||||
title = "Browse Lospec Palettes"
|
||||
initial_position = 2
|
||||
size = Vector2i(1000, 600)
|
||||
visible = false
|
||||
wrap_controls = true
|
||||
script = ExtResource("7_ob3x8")
|
||||
colors = NodePath("..")
|
||||
browse_http = NodePath("HTTPRequest")
|
||||
browse_preview_scene = ExtResource("13_a5syi")
|
||||
preview_container = NodePath("MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer")
|
||||
alert_popup = NodePath("../AlertPopup")
|
||||
loading_screen = NodePath("MarginContainer/Loading")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Colors/BrowsePopup"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/margin_left = 25
|
||||
theme_override_constants/margin_top = 25
|
||||
theme_override_constants/margin_right = 25
|
||||
theme_override_constants/margin_bottom = 25
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Colors/BrowsePopup/MarginContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="Colors/BrowsePopup/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Colors/BrowsePopup/MarginContainer/VBoxContainer/ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="LoadMore" type="Button" parent="Colors/BrowsePopup/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Load More"
|
||||
|
||||
[node name="Loading" type="ColorRect" parent="Colors/BrowsePopup/MarginContainer" node_paths=PackedStringArray("loading_text")]
|
||||
layout_mode = 2
|
||||
color = Color(0.878431, 0.878431, 0.878431, 1)
|
||||
script = ExtResource("9_xvc3k")
|
||||
loading_text = NodePath("Label")
|
||||
|
||||
[node name="Label" type="Label" parent="Colors/BrowsePopup/MarginContainer/Loading"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -191.0
|
||||
offset_top = -68.5
|
||||
offset_right = 191.0
|
||||
offset_bottom = 68.5
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "Loading"
|
||||
label_settings = SubResource("LabelSettings_26hsa")
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="HTTPRequest" type="HTTPRequest" parent="Colors/BrowsePopup"]
|
||||
|
||||
[connection signal="request_completed" from="Colors/HTTPRequest" to="Colors/HTTPRequest" method="_on_request_completed"]
|
||||
[connection signal="toggled" from="Colors/Palette/CustomPicker/CustomPickerCheckBox" to="Colors" method="_on_custom_picker_check_box_toggled"]
|
||||
[connection signal="item_activated" from="Colors/Palette/SavedPalettes" to="Colors" method="_on_saved_palettes_item_activated"]
|
||||
[connection signal="pressed" from="Colors/Palette/IO/New" to="Colors" method="_on_new_palette_pressed"]
|
||||
[connection signal="pressed" from="Colors/Palette/IO/Load" to="Colors" method="_on_load_palette_pressed"]
|
||||
[connection signal="pressed" from="Colors/Palette/IO/Delete" to="Colors" method="_on_delete_palette_pressed"]
|
||||
[connection signal="text_submitted" from="Colors/Palette/Search/LineEdit" to="Colors" method="_on_line_edit_text_submitted"]
|
||||
[connection signal="pressed" from="Colors/Palette/Search/Search" to="Colors" method="_on_search_pressed"]
|
||||
[connection signal="pressed" from="Colors/Palette/BrowsePalettesButton" to="Colors" method="_on_browse_palettes_button_pressed"]
|
||||
[connection signal="pressed" from="Colors/Palette/HBoxContainer/Save" to="Colors" method="_on_save_palette_pressed"]
|
||||
[connection signal="pressed" from="Colors/Palette/HBoxContainer/Clear" to="Colors" method="_on_clear_pressed"]
|
||||
[connection signal="pressed" from="Colors/Palette/Container/VBoxContainer/SaveToEditor/SaveToEditorButton" to="Colors" method="_on_save_to_editor_button_pressed"]
|
||||
[connection signal="pressed" from="Colors/Palette/Container/VBoxContainer/RestartEditor/RestartEditorButton" to="Colors" method="_on_restart_editor_pressed"]
|
||||
[connection signal="close_requested" from="Colors/AlertPopup" to="Colors/AlertPopup" method="_on_close_requested"]
|
||||
[connection signal="pressed" from="Colors/AlertPopup/Control/CloseButton" to="Colors/AlertPopup" method="_on_close_button_pressed"]
|
||||
[connection signal="color_changed" from="Colors/ColorPickerPopup/ColorPicker" to="Colors" method="_on_color_picker_color_changed"]
|
||||
[connection signal="close_requested" from="Colors/BrowsePopup" to="Colors/BrowsePopup" method="_on_close_requested"]
|
||||
[connection signal="focus_exited" from="Colors/BrowsePopup" to="Colors/BrowsePopup" method="_on_focus_exited"]
|
||||
[connection signal="size_changed" from="Colors/BrowsePopup" to="Colors/BrowsePopup" method="_on_size_changed"]
|
||||
[connection signal="visibility_changed" from="Colors/BrowsePopup" to="Colors/BrowsePopup" method="_on_visibility_changed"]
|
||||
[connection signal="pressed" from="Colors/BrowsePopup/MarginContainer/VBoxContainer/LoadMore" to="Colors/BrowsePopup" method="_on_load_more_pressed"]
|
||||
[connection signal="visibility_changed" from="Colors/BrowsePopup/MarginContainer/Loading" to="Colors/BrowsePopup/MarginContainer/Loading" method="_on_visibility_changed"]
|
||||
[connection signal="request_completed" from="Colors/BrowsePopup/HTTPRequest" to="Colors/BrowsePopup" method="_on_http_request_completed"]
|
||||
@ -1,31 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://x40sgfxkrskd"]
|
||||
|
||||
[ext_resource type="Texture2D" path="res://addons/PaletteTools/Images/Plus.png" id="1_o5f0x"]
|
||||
|
||||
[node name="ColorSample" type="MarginContainer"]
|
||||
custom_minimum_size = Vector2(50, 50)
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -12.5
|
||||
offset_top = -12.5
|
||||
offset_right = 12.5
|
||||
offset_bottom = 12.5
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/margin_left = 5
|
||||
theme_override_constants/margin_top = 5
|
||||
theme_override_constants/margin_right = 5
|
||||
theme_override_constants/margin_bottom = 5
|
||||
|
||||
[node name="Color" type="TextureButton" parent="."]
|
||||
texture_filter = 1
|
||||
custom_minimum_size = Vector2(40, 40)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Add color to palette"
|
||||
mouse_default_cursor_shape = 2
|
||||
texture_normal = ExtResource("1_o5f0x")
|
||||
ignore_texture_size = true
|
||||
stretch_mode = 5
|
||||
@ -1,17 +0,0 @@
|
||||
@tool
|
||||
extends PopupPanel
|
||||
|
||||
@export var alert_text_label: Label
|
||||
|
||||
|
||||
func alert(message: String) -> void:
|
||||
alert_text_label.text = message
|
||||
show()
|
||||
|
||||
|
||||
func _on_close_button_pressed() -> void:
|
||||
hide()
|
||||
|
||||
|
||||
func _on_close_requested() -> void:
|
||||
hide()
|
||||
@ -1 +0,0 @@
|
||||
uid://cd6rspiirpfy5
|
||||
@ -1,14 +0,0 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
const Palette := preload("res://addons/PaletteTools/Scripts/palette_tool.gd")
|
||||
|
||||
@export var info_label: Label
|
||||
@export var color_container: Control
|
||||
|
||||
var colors: Palette
|
||||
var palette_obj: Dictionary
|
||||
|
||||
|
||||
func _on_palette_select_pressed() -> void:
|
||||
colors.import_palette_from_browse(palette_obj)
|
||||
@ -1 +0,0 @@
|
||||
uid://pagg2ueykteh
|
||||
@ -1,94 +0,0 @@
|
||||
@tool
|
||||
extends Window
|
||||
|
||||
const Palette := preload("res://addons/PaletteTools/Scripts/palette_tool.gd")
|
||||
const Alert := preload("res://addons/PaletteTools/Scripts/alert_popup_panel.gd")
|
||||
const BrowsePreview := preload("res://addons/PaletteTools/Scripts/browse_palette_preview.gd")
|
||||
|
||||
@export var colors: Palette
|
||||
@export var browse_http: HTTPRequest
|
||||
@export var browse_preview_scene: PackedScene
|
||||
@export var preview_container: Control
|
||||
@export var alert_popup: Alert
|
||||
@export var loading_screen: Control
|
||||
|
||||
var current_page: int = 0
|
||||
var close_delay: int = 4
|
||||
|
||||
|
||||
func get_palette_list() -> void:
|
||||
browse_http.request(
|
||||
"https://lospec.com/palette-list/load?colorNumberFilterType=any&colorNumber=8&page="
|
||||
+ str(current_page)
|
||||
+ "&tag=&sortingType=downloads"
|
||||
)
|
||||
current_page += 1
|
||||
|
||||
|
||||
func display_preview(palette_obj: Dictionary) -> void:
|
||||
var prev: BrowsePreview = browse_preview_scene.instantiate()
|
||||
prev.colors = colors
|
||||
prev.palette_obj = palette_obj
|
||||
prev.info_label.text = palette_obj.name + " by: " + palette_obj.author
|
||||
preview_container.add_child(prev)
|
||||
for col in palette_obj.colors:
|
||||
var new_color := ColorRect.new()
|
||||
new_color.custom_minimum_size = Vector2(75, 75)
|
||||
new_color.color = Color.from_string(col, Color.WHITE)
|
||||
prev.color_container.add_child(new_color)
|
||||
|
||||
|
||||
func _on_close_button_pressed() -> void:
|
||||
visible = false
|
||||
|
||||
|
||||
func _on_http_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
var html_text := body.get_string_from_utf8()
|
||||
var json := JSON.new()
|
||||
var err := json.parse(html_text)
|
||||
if err != OK:
|
||||
alert_popup.alert("Error getting palettes")
|
||||
return
|
||||
|
||||
loading_screen.visible = false
|
||||
for pal in json.data.palettes:
|
||||
var user := "Lospec"
|
||||
var slug := ""
|
||||
if pal.has("user"):
|
||||
user = pal.user.name
|
||||
if pal.has("slug"):
|
||||
slug = pal.slug
|
||||
var palette_obj := {
|
||||
"name": pal.title,
|
||||
"author": user,
|
||||
"colors": pal.colors,
|
||||
"slug": slug
|
||||
}
|
||||
|
||||
display_preview(palette_obj)
|
||||
|
||||
|
||||
func _on_load_more_pressed() -> void:
|
||||
get_palette_list()
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
if visible and current_page == 0:
|
||||
get_palette_list()
|
||||
|
||||
|
||||
func _on_close_requested() -> void:
|
||||
hide()
|
||||
|
||||
|
||||
func _on_focus_exited() -> void:
|
||||
while close_delay > 0:
|
||||
close_delay -= 1
|
||||
await get_tree().create_timer(1).timeout
|
||||
if not has_focus():
|
||||
hide()
|
||||
close_delay = 3
|
||||
|
||||
|
||||
func _on_size_changed() -> void:
|
||||
close_delay = 3
|
||||
@ -1 +0,0 @@
|
||||
uid://cas0s5bfj0txi
|
||||
@ -1,6 +0,0 @@
|
||||
@tool
|
||||
extends MarginContainer
|
||||
|
||||
@export var color_picker_button: ColorPickerButton
|
||||
@export var remove_button: TextureButton
|
||||
@export var remove_button_text: Label
|
||||
@ -1 +0,0 @@
|
||||
uid://8epr28t2njss
|
||||
@ -1,15 +0,0 @@
|
||||
@tool
|
||||
extends PopupPanel
|
||||
|
||||
@export var color_picker: ColorPicker
|
||||
@export var saved_palettes: ItemList
|
||||
@export var apply_palette_button: Button
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
hide()
|
||||
min_size = Vector2i(320, 675)
|
||||
size = Vector2i(320, 675)
|
||||
if DisplayServer.screen_get_size().x > 2000:
|
||||
min_size *= 2
|
||||
size *= 2
|
||||
@ -1 +0,0 @@
|
||||
uid://b3jncwvxjir6f
|
||||
@ -1,67 +0,0 @@
|
||||
extends EditorProperty
|
||||
|
||||
signal mouse_released
|
||||
|
||||
const CustomPalette := preload("res://addons/PaletteTools/Scripts/custom_picker.gd")
|
||||
|
||||
var property_control: Control
|
||||
var updating := false
|
||||
var custom_palette: CustomPalette
|
||||
|
||||
|
||||
func _init(cust_palette: CustomPalette, obj: Object, named = null) -> void:
|
||||
property_control = load("res://addons/PaletteTools/Scenes/custom_palette.tscn").instantiate()
|
||||
custom_palette = cust_palette
|
||||
if named and obj[named]:
|
||||
property_control.color = obj[named]
|
||||
else:
|
||||
property_control.color = Color.BLACK
|
||||
add_child(property_control)
|
||||
add_focusable(property_control)
|
||||
property_control.get_child(0).pressed.connect(_on_button_pressed)
|
||||
resource_selected.connect(resource_prop)
|
||||
|
||||
|
||||
func resource_prop(path: String, prop: Resource) -> void:
|
||||
if prop.get(path):
|
||||
var p := prop.get(path)
|
||||
if p is Vector4:
|
||||
property_control.color = Color(p.x, p.y, p.z, p.w)
|
||||
else:
|
||||
property_control.color = p
|
||||
else:
|
||||
property_control.color = Color.WHITE
|
||||
|
||||
|
||||
func _on_button_pressed() -> void:
|
||||
if not custom_palette:
|
||||
push_warning(
|
||||
'Error in Palette Tools addon: If the "Palette Tools" addon was just activated, '
|
||||
+ "please select a different node before trying to edit color properties of this one. "
|
||||
+ "This error happens becuase the inspector was loaded before the addon. If you didn't "
|
||||
+ "just activate the addon or start the Godot editor, you can try turning the addon "
|
||||
+ "on and off. If the problem persists, you may need to disable the addon. If this happens, "
|
||||
+ "please report any relevant info so a fix can be made. Thanks!"
|
||||
)
|
||||
return
|
||||
custom_palette.popup(Rect2(get_global_mouse_position() - Vector2(size.x, (size.y * 20)), size))
|
||||
custom_palette.color_picker.color = get_edited_object()[get_edited_property()]
|
||||
custom_palette.popup_hide.connect(cust_palette_closed, CONNECT_ONE_SHOT)
|
||||
custom_palette.color_picker.color_changed.connect(cust_palette_changed)
|
||||
|
||||
|
||||
func cust_palette_changed(new_color: Color) -> void:
|
||||
property_control.color = new_color
|
||||
|
||||
|
||||
func cust_palette_closed() -> void:
|
||||
custom_palette.color_picker.color_changed.disconnect(cust_palette_changed)
|
||||
property_control.color = custom_palette.color_picker.color
|
||||
emit_changed(get_edited_property(), property_control.color)
|
||||
|
||||
|
||||
func _update_property() -> void:
|
||||
if get_edited_object() and get_edited_object()[get_edited_property()]:
|
||||
property_control.color = get_edited_object()[get_edited_property()]
|
||||
else:
|
||||
property_control.color = Color.BLACK
|
||||
@ -1 +0,0 @@
|
||||
uid://bnpe28125rr05
|
||||
@ -1,29 +0,0 @@
|
||||
@tool
|
||||
extends HTTPRequest
|
||||
|
||||
const Palette := preload("res://addons/PaletteTools/Scripts/palette_tool.gd")
|
||||
|
||||
@export var colors: Palette
|
||||
|
||||
var searching := false
|
||||
|
||||
|
||||
func get_palette(url: String) -> void:
|
||||
if searching:
|
||||
return
|
||||
searching = true
|
||||
if url.ends_with("/"):
|
||||
url = url.left(-1)
|
||||
var error := request(url + ".json")
|
||||
if error != OK:
|
||||
push_error("An error occurred in the HTTP request.")
|
||||
|
||||
|
||||
func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
searching = false
|
||||
var json := FileAccess.get_file_as_string(download_file)
|
||||
var json_obj := JSON.parse_string(json)
|
||||
|
||||
colors.preview_colors(json_obj.colors)
|
||||
colors.p_name_text.text = json_obj.name
|
||||
colors.p_author_text.text = json_obj.author
|
||||
@ -1 +0,0 @@
|
||||
uid://bm2n1k624d6y1
|
||||
@ -1,17 +0,0 @@
|
||||
@tool
|
||||
extends ColorRect
|
||||
|
||||
@export var loading_text: Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
visible = true
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
while visible:
|
||||
await get_tree().create_timer(.7).timeout
|
||||
if loading_text.text.ends_with("..."):
|
||||
loading_text.text.replace("...", "")
|
||||
else:
|
||||
loading_text.text += "."
|
||||