Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate category enums from category heirarchy #169

Merged
merged 3 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 62 additions & 38 deletions addons/pandora/api.gd
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
@tool
extends Node


const EntityIdFileGenerator = preload("res://addons/pandora/util/entity_id_file_generator.gd")
const CategoryIdFileGenerator = preload("res://addons/pandora/util/category_id_file_generator.gd")
const ScriptUtil = preload("res://addons/pandora/util/script_util.gd")


signal data_loaded
signal data_loaded_failure
signal entity_added(entity:PandoraEntity)
signal entity_added(entity: PandoraEntity)
signal import_success(imported_count: int)
signal import_failed(reason: String)
signal import_calculation_ended(import_info: Dictionary)
signal import_calculation_failed(reason: String)
signal import_progress


var _context_manager: PandoraContextManager
var _storage: PandoraJsonDataStorage
var _id_generator: PandoraIDGenerator
var _entity_backend: PandoraEntityBackend


var _loaded = false
var _backend_load_state:PandoraEntityBackend.LoadState = PandoraEntityBackend.LoadState.NOT_LOADED
var _backend_load_state: PandoraEntityBackend.LoadState = PandoraEntityBackend.LoadState.NOT_LOADED


func _enter_tree() -> void:
Expand All @@ -48,19 +45,19 @@ func get_context_id() -> String:
return _context_manager.get_context_id()


func set_context_id(context_id:String) -> void:
func set_context_id(context_id: String) -> void:
_context_manager.set_context_id(context_id)


func create_entity(name:String, category:PandoraCategory) -> PandoraEntity:
func create_entity(name: String, category: PandoraCategory) -> PandoraEntity:
return _entity_backend.create_entity(name, category)


func create_category(name:String, parent_category:PandoraCategory = null) -> PandoraCategory:
func create_category(name: String, parent_category: PandoraCategory = null) -> PandoraCategory:
return _entity_backend.create_category(name, parent_category)


func create_property(on_category:PandoraCategory, name:String, type:String) -> PandoraProperty:
func create_property(on_category: PandoraCategory, name: String, type: String) -> PandoraProperty:
return _entity_backend.create_property(on_category, name, type)


Expand All @@ -80,41 +77,54 @@ func regenerate_property_id(property: PandoraProperty) -> void:
_entity_backend.regenerate_property_id(property)


func delete_category(category:PandoraCategory) -> void:
func delete_category(category: PandoraCategory) -> void:
_entity_backend.delete_category(category)


func delete_entity(entity:PandoraEntity) -> void:
func delete_entity(entity: PandoraEntity) -> void:
_entity_backend.delete_entity(entity)


func move_entity(source: PandoraEntity, target: PandoraEntity, drop_section: PandoraEntityBackend.DropSection) -> void:

func move_entity(
source: PandoraEntity, target: PandoraEntity, drop_section: PandoraEntityBackend.DropSection
) -> void:
_entity_backend.move_entity(source, target, drop_section)

func get_entity(entity_id:String) -> PandoraEntity:


func get_entity(entity_id: String) -> PandoraEntity:
return _entity_backend.get_entity(entity_id)

func get_category(category_id:String) -> PandoraCategory:


func get_category(category_id: String) -> PandoraCategory:
return _entity_backend.get_category(category_id)

func get_property(property_id:String) -> PandoraProperty:

func get_property(property_id: String) -> PandoraProperty:
return _entity_backend.get_property(property_id)


func get_all_roots() -> Array[PandoraCategory]:
return _entity_backend.get_all_roots()


func get_all_categories(filter:PandoraCategory = null, sort:Callable = func(a,b): return false) -> Array[PandoraEntity]:
func get_all_categories(
filter: PandoraCategory = null, sort: Callable = func(a, b): return false
) -> Array[PandoraEntity]:
return _entity_backend.get_all_categories(filter, sort)


func get_all_entities(filter:PandoraCategory = null, sort:Callable = func(a,b): return false) -> Array[PandoraEntity]:
func get_all_entities(
filter: PandoraCategory = null, sort: Callable = func(a, b): return false
) -> Array[PandoraEntity]:
return _entity_backend.get_all_entities(filter, sort)

func check_if_properties_will_change_on_move(source:PandoraEntity, target:PandoraEntity, drop_section:PandoraEntityBackend.DropSection) -> bool:

func check_if_properties_will_change_on_move(
source: PandoraEntity, target: PandoraEntity, drop_section: PandoraEntityBackend.DropSection
) -> bool:
return _entity_backend.check_if_properties_will_change_on_move(source, target, drop_section)


func load_data_async() -> void:
var thread = Thread.new()
if thread.start(load_data) != 0:
Expand Down Expand Up @@ -150,25 +160,36 @@ func save_data() -> void:
_storage.store_all_data(all_object_data, _context_manager.get_context_id())

EntityIdFileGenerator.regenerate_id_files(get_all_roots())
CategoryIdFileGenerator.regenerate_category_id_file(get_all_roots())


func calculate_import_data(path: String) -> int:
var imported_data = _storage._load_from_file(path)
var total_items = 0
if not imported_data.has("_entity_data"):
import_calculation_failed.emit("Provided file is invalid or is corrupted.")
else:
total_items = imported_data["_entity_data"]["_categories"].size() + imported_data["_entity_data"]["_entities"].size() + imported_data["_entity_data"]["_properties"].size()
total_items = (
imported_data["_entity_data"]["_categories"].size()
+ imported_data["_entity_data"]["_entities"].size()
+ imported_data["_entity_data"]["_properties"].size()
)
if total_items == 0:
import_calculation_failed.emit("Provided file is empty.")
else:
import_calculation_ended.emit({
"total_categories": imported_data["_entity_data"]["_categories"].size(),
"total_entities": imported_data["_entity_data"]["_entities"].size(),
"total_properties": imported_data["_entity_data"]["_properties"].size(),
"path": path,
})
(
import_calculation_ended
. emit(
{
"total_categories": imported_data["_entity_data"]["_categories"].size(),
"total_entities": imported_data["_entity_data"]["_entities"].size(),
"total_properties": imported_data["_entity_data"]["_properties"].size(),
"path": path,
}
)
)
return total_items


func import_data(path: String) -> int:
var imported_data = _storage._load_from_file(path)
Expand All @@ -180,31 +201,34 @@ func import_data(path: String) -> int:
if imported_count == -1:
import_failed.emit("No data found in file.")
return 0

import_success.emit(imported_count)

return imported_count


func is_loaded() -> bool:
return _loaded
func serialize(instance:PandoraEntity) -> Dictionary:


func serialize(instance: PandoraEntity) -> Dictionary:
if instance is PandoraCategory:
push_warning("Cannot serialize a category!")
return {}
if not instance.is_instance():
var new_instance = instance.instantiate()
return new_instance.save_data()
return instance.save_data()
func deserialize(data:Dictionary) -> PandoraEntity:


func deserialize(data: Dictionary) -> PandoraEntity:
if not _loaded:
push_warning("Pandora - cannot deserialize: data not initialized yet.")
return null
if not data.has("_instanced_from_id"):
push_error("Unable to deserialize data! Not an instance! Call PandoraEntity.instantiate() to create instances.")
push_error(
"Unable to deserialize data! Not an instance! Call PandoraEntity.instantiate() to create instances."
)
return
var entity = Pandora.get_entity(data["_instanced_from_id"])
if not entity:
Expand Down
92 changes: 92 additions & 0 deletions addons/pandora/util/category_id_file_generator.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
## Generates a .gd file that allows for easier access
## of categories and subcategories of the data
static func regenerate_category_id_file(root_categories: Array[PandoraCategory]) -> void:
var root_category_tuples: Array[CategoryTuple] = []
# { parent: [CategoryTuple] }
var subcategory_tuples = {}

for category in root_categories:
root_category_tuples.append(
CategoryTuple.new(category.get_entity_name(), category.get_entity_id())
)
generate_sub_category_tuples(
category.get_entity_name(), category._children, subcategory_tuples
)

generate_category_id_file(root_category_tuples, subcategory_tuples)


static func generate_sub_category_tuples(
parent_category: String, entities: Array[PandoraEntity], subcategory_tuples: Dictionary
) -> void:
var category_tuples: Array[CategoryTuple] = []
subcategory_tuples[parent_category] = category_tuples

for entity in entities:
if entity is PandoraCategory:
var entity_name = entity.get_entity_name()

subcategory_tuples[parent_category].append(
CategoryTuple.new(entity_name, entity.get_entity_id())
)

# If we already have an existing category name, we need to rename it with the parent category
if subcategory_tuples.has(entity_name):
entity_name = "%s_%s" % [parent_category, entity_name]

generate_sub_category_tuples(entity_name, entity._children, subcategory_tuples)


static func generate_category_id_file(
root_category_tuples: Array[CategoryTuple], subcategory_tuples: Dictionary
) -> void:
var file_path = "res://pandora/categories.gd"
if not DirAccess.dir_exists_absolute("res://pandora"):
DirAccess.make_dir_absolute("res://pandora")

var file_access = FileAccess.open(file_path, FileAccess.WRITE)
file_access.store_line("# Do not modify! Auto-generated file.")
file_access.store_line("class_name PandoraCategories\n\n")

# Per GDScript style guide we put consts at the top of the file so we do the root_category_tuples first
for category_tuple in root_category_tuples:
var line = (
'const %s = "%s"'
% [
category_tuple.category_name.to_upper().replace(" ", "_"),
category_tuple.category_id
]
)
file_access.store_line(line)

file_access.store_line("\n")

# We then do all the inline classes
for parent_category in subcategory_tuples:
if subcategory_tuples[parent_category].size() == 0:
continue

var line = "class %sCategories:" % parent_category.to_pascal_case()
file_access.store_line(line)

for category_tuple in subcategory_tuples[parent_category]:
line = (
' const %s = "%s"'
% [
category_tuple.category_name.to_upper().replace(" ", "_"),
category_tuple.category_id
]
)
file_access.store_line(line)
file_access.store_line("\n")

file_access.close()


class CategoryTuple:
var category_name: String
var category_id: String

func _init(category_name: String, category_id: String) -> void:
self.category_name = category_name
self.category_id = category_id
7 changes: 5 additions & 2 deletions docs/api/access.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ In this example, the `Damage` property of the `SWORD` entity is fetched, demonst

Pandora allows you to define entities on nodes directly inside the editor:

- The exported type **must** extend `PandoraEntity`
- The exported type **must** extend `PandoraEntity`
- The exported type **must** be a `@tool` script

This can be done as follows:

```gdscript
extends Node2D

Expand All @@ -39,6 +40,7 @@ extends Node2D
func _ready() -> void:
var instance:PandoraEntity = item.instantiate()
```

Within the Godot node editor properties, then select the entity of your choice from the list. Pandora automatically filters the entities depending on the type provided:

![custom-type-exports](../assets/custom_type_exports.gif)
Expand All @@ -55,6 +57,7 @@ if entity is CustomType:
# 1. a parent category of entity has the CustomType script set
# 2. CustomType extends PandoraEntity
```

✅ **Advantage**: type-safe and allows for auto-completion</br>
😕 **Downside**: requires extra scripts to do type-checks

Expand Down Expand Up @@ -86,4 +89,4 @@ if entity.is_category(Items.CATEGORY_ORES):
```

✅ **Advantage**: no additional setup required (quick)</br>
😕 **Downside**: currently not possible, requires [#63](https://github.com/bitbrain/pandora/issues/63) to be done first!
😕 **Downside**: category generation is still alpha and the api may change
Loading
Loading