miggor-StreamGraph/classes/deck/node_db.gd

279 lines
8.3 KiB
GDScript3
Raw Normal View History

# (c) 2023-present Eroax
# (c) 2023-present Yagich
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
class_name NodeDB
## Path to search for node files.
const BASE_NODE_PATH := "res://classes/deck/nodes/"
## Filepath where the index of [NodeDB.NodeDescriptor]s are saved to avoid reloading
## everything each run.
## @experimental
2023-07-21 08:39:38 +02:00
const NODE_INDEX_CACHE_PATH := "user://nodes_index.json"
## Filepath where the the list of favorite nodes is stored.
const FAVORITE_NODES_PATH := "user://favorite_nodes.json"
## A map of [code]snake_case[/code] category names for proper capitalization.
const CATEGORY_CAPITALIZATION := {
"obs": "OBS"
}
## A list of nodes that the user marked favorite in the [AddNodeMenu].
static var favorite_nodes: Array[String]
2023-07-21 07:30:12 +02:00
# Dictionary[node_type, NodeDescriptor]
## The node index. Maps [member DeckNode.node_type]s to [NodeDB.NodeDescriptor].
static var nodes: Dictionary = {}
static var libraries: Dictionary = {}
static func init() -> void:
load_favorites()
#if load_node_index():
#return
2023-07-21 08:39:38 +02:00
create_descriptors(BASE_NODE_PATH)
reload_libraries()
save_node_index()
## Fills the [member nodes] index.
static func create_descriptors(path: String) -> void:
var dir := DirAccess.open(path)
if not dir:
return
dir.list_dir_begin()
var current_file := dir.get_next()
while current_file != "":
if dir.current_is_dir():
create_descriptors(path.path_join(current_file))
elif current_file.ends_with(".gd"):
var script_path := path.path_join(current_file)
var node: DeckNode = load(script_path).new() as DeckNode
var aliases: String = node.aliases.reduce(
func(accum, el):
return accum + el
, "")
var descriptor := NodeDescriptor.new(
script_path,
node.name,
node.node_type,
node.description,
aliases,
path.get_slice("/", path.get_slice_count("/") - 1),
node.appears_in_search,
false,
)
nodes[node.node_type] = descriptor
print_verbose("NodeDB: freeing node %s, id %s" % [node.node_type, node.get_instance_id()])
node.free()
current_file = dir.get_next()
## Fills the [member libraries] index.
static func create_lib_descriptors(path: String) -> void:
var dir := DirAccess.open(path)
if not dir:
return
dir.list_dir_begin()
var current_file := dir.get_next()
while current_file != "":
if dir.current_is_dir():
create_lib_descriptors(path.path_join(current_file))
elif current_file.ends_with(".deck"):
var load_path := path.path_join(current_file)
var f := FileAccess.open(load_path, FileAccess.READ)
var deck: Dictionary = JSON.parse_string(f.get_as_text())
if not deck.deck.has("library"):
current_file = dir.get_next()
continue
var type := current_file.trim_suffix(".deck")
if nodes.has(type):
DeckHolder.logger.toast_error("Library group '%s' collides with a node with the same type." % type)
current_file = dir.get_next()
continue
if libraries.has(type):
DeckHolder.logger.toast_error("Library group '%s' collides with a library group with the same type." % type)
current_file = dir.get_next()
continue
var lib = deck.deck.library
var aliases: String = lib.lib_aliases.reduce(
func(accum, el):
return accum + el
, "")
var descriptor := NodeDescriptor.new(
load_path,
lib.lib_name,
type,
lib.lib_description,
aliases,
path.get_slice("/", path.get_slice_count("/") - 1),
true,
true,
)
libraries[descriptor.type] = descriptor
current_file = dir.get_next()
2023-07-21 08:39:38 +02:00
static func reload_libraries() -> void:
libraries.clear()
for path in StreamGraphConfig.get_library_search_paths():
create_lib_descriptors(path)
## Instantiates a [DeckNode] from a given [param node_type]. See [member DeckNode.node_type].
static func instance_node(node_type: String) -> DeckNode:
if not nodes.has(node_type):
return null
return load(nodes[node_type]["script_path"]).new()
2023-07-21 07:30:12 +02:00
## Saves the index of all loaded nodes to [member NODE_INDEX_CACHE_PATH].
static func save_node_index() -> void:
2023-07-21 08:39:38 +02:00
var d := {}
for node_type in nodes:
var nd: NodeDescriptor = nodes[node_type] as NodeDescriptor
d[node_type] = nd.to_dictionary()
var json := JSON.stringify(d, "\t")
var f := FileAccess.open(NODE_INDEX_CACHE_PATH, FileAccess.WRITE)
f.store_string(json)
## Loads the node index from [member NODE_INDEX_CACHE_PATH]. Returns [code]true[/code]
## if the index was found on the file system.
static func load_node_index() -> bool:
2023-07-21 08:39:38 +02:00
var f := FileAccess.open(NODE_INDEX_CACHE_PATH, FileAccess.READ)
if f == null:
DeckHolder.logger.log_system("node index file does not exist", Logger.LogType.WARN)
2023-07-21 08:39:38 +02:00
return false
var data: Dictionary = JSON.parse_string(f.get_as_text()) as Dictionary
if data.is_empty():
DeckHolder.logger.log_system("node index file exists, but is empty", Logger.LogType.ERROR)
2023-07-21 08:39:38 +02:00
return false
for node_type in data:
var nd_dict: Dictionary = data[node_type]
var nd := NodeDescriptor.from_dictionary(nd_dict)
nodes[node_type] = nd
DeckHolder.logger.log_system("node index file exists, loaded")
2023-07-21 08:39:38 +02:00
return true
## Marks a [member DeckNode.node_type] as "favorite" for use in
## [AddNodeMenu].
static func set_node_favorite(node_type: String, favorite: bool) -> void:
if (favorite and node_type in favorite_nodes) or (not favorite and node_type not in favorite_nodes):
return
if favorite:
favorite_nodes.append(node_type)
else:
favorite_nodes.erase(node_type)
var f := FileAccess.open(FAVORITE_NODES_PATH, FileAccess.WRITE)
f.store_string(JSON.stringify(favorite_nodes, "\t"))
## Loads the list of favorite [memeber DeckNode.node_type]s from [member FAVORITE_NODES_PATH].
static func load_favorites() -> void:
var f := FileAccess.open(FAVORITE_NODES_PATH, FileAccess.READ)
if not f:
return
var data: Array = JSON.parse_string(f.get_as_text())
favorite_nodes.clear()
favorite_nodes.assign(data)
## Returns [code]true[/code] if the specified [member DeckNode.node_type] is marked favorite
## by the user.
static func is_node_favorite(node_type: String) -> bool:
return node_type in favorite_nodes
static func is_library(type: String) -> bool:
return libraries.has(type)
## Returns a capitalized category string.
static func get_category_capitalization(category: String) -> String:
return CATEGORY_CAPITALIZATION.get(category, category.capitalize())
## Used for storing the shorthand data of a [DeckNode].
2023-07-21 07:30:12 +02:00
class NodeDescriptor:
## Default name of the [DeckNode] type this is storing properties of.
2023-07-21 07:30:12 +02:00
var name: String
## The node type of the [DeckNode] reference. See [member DeckNode.node_type].
var type: String
## The description of the [DeckNode] reference. See [member DeckNode.description].
2023-07-21 07:30:12 +02:00
var description: String
## The aliases of the [DeckNode] reference. Stored as a flattened string of
## the [member DeckNode.aliases] array.
2023-07-21 07:30:12 +02:00
var aliases: String
## The category of the [DeckNode] reference. See [member DeckNode.category].
var category: String
## Whether this [DeckNode] reference will appear when searching. See [member DeckNode.appears_in_search].
var appears_in_search: bool
var is_library: bool
## Stores the path to this node's script for later instantiation.
2023-07-21 07:30:12 +02:00
var script_path: String
func _init(
p_script_path: String,
p_name: String,
p_type: String,
p_description: String,
p_aliases: String,
p_category: String,
p_appears_in_search: bool,
p_is_library: bool,
) -> void:
script_path = p_script_path
2023-07-21 07:30:12 +02:00
name = p_name
type = p_type
description = p_description
aliases = p_aliases
category = p_category
appears_in_search = p_appears_in_search
is_library = p_is_library
2023-07-21 08:39:38 +02:00
## Returns a [Dictionary] representation of this node descriptor.
2023-07-21 08:39:38 +02:00
func to_dictionary() -> Dictionary:
var d := {
"name": name,
"type": type,
2023-07-21 08:39:38 +02:00
"description": description,
"aliases": aliases,
"script_path": script_path,
"category": category,
"appears_in_search": appears_in_search,
"is_library": is_library,
2023-07-21 08:39:38 +02:00
}
return d
## Creates a new [NodeDB.NodeDescriptor] from a given [Dictionary] of properties.
2023-07-21 08:39:38 +02:00
static func from_dictionary(data: Dictionary) -> NodeDescriptor:
var nd := NodeDescriptor.new(
data.get("script_path", ""),
data.get("name", ""),
data.get("type", ""),
2023-07-21 08:39:38 +02:00
data.get("description", ""),
data.get("aliases", ""),
data.get("category", ""),
data.get("appears_in_search", false),
data.get("is_library", false)
)
2023-07-21 08:39:38 +02:00
return nd