Merge pull request #68 from Nitwel/persistent

Make entities and room persistent
This commit is contained in:
Nitwel 2023-12-09 18:46:53 +01:00 committed by GitHub
commit 3f79fcaf96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 242 additions and 28 deletions

View File

@ -148,6 +148,10 @@ Thus I've decided to use a custom event system that is similar to the one used i
| `ui_focus_skip` | Focus checking on this element will be skipped | | `ui_focus_skip` | Focus checking on this element will be skipped |
| `ui_focus_stop` | The focus will not be reset. Useful for keyboard | | `ui_focus_stop` | The focus will not be reset. Useful for keyboard |
### Saving and Loading
In order for an entity to be saved, it has to implement the `_save` function returning a dictionary of the data that should be saved.
When loading, first the saved node gets instantiated, then either the `_load` function is called with the saved data, or when no `_load` function is implemented, the saved data directly applied to the node.
### Functions ### Functions

View File

@ -55,4 +55,10 @@ func _on_click(event):
attributes["brightness"] = int(brightness) attributes["brightness"] = int(brightness)
HomeApi.set_state(entity_id, "on" if !state else "off", attributes) HomeApi.set_state(entity_id, "on" if !state else "off", attributes)
set_state(!state, brightness) set_state(!state, brightness)
func _save():
return {
"transform": transform,
"entity_id": entity_id
}

View File

@ -86,3 +86,9 @@ func load_image(url: String):
var texture = ImageTexture.create_from_image(image) var texture = ImageTexture.create_from_image(image)
logo.texture = texture logo.texture = texture
logo.pixel_size = pixel_size logo.pixel_size = pixel_size
func _save():
return {
"transform": transform,
"entity_id": entity_id
}

View File

@ -22,3 +22,9 @@ func set_text(stateInfo):
text += " " + stateInfo["attributes"]["unit_of_measurement"] text += " " + stateInfo["attributes"]["unit_of_measurement"]
label.text = text label.text = text
func _save():
return {
"transform": transform,
"entity_id": entity_id
}

View File

@ -31,3 +31,9 @@ func _on_click(event):
func _on_request_completed(): func _on_request_completed():
pass pass
func _save():
return {
"transform": transform,
"entity_id": entity_id
}

View File

@ -54,5 +54,4 @@ func vector_key_mapping(key_positive_x: int, key_negative_x: int, key_positive_y
if vec: if vec:
vec = vec.normalized() vec = vec.normalized()
return vec return vec

View File

@ -154,7 +154,7 @@ func _on_entity_click(entity_name):
return return
entity.set_position(global_position) entity.set_position(global_position)
get_node("/root").add_child(entity) get_node("/root/Main").add_child(entity)
func clear_menu(): func clear_menu():
for child in devices_node.get_children(): for child in devices_node.get_children():

View File

@ -41,31 +41,32 @@ func _ready():
wall_mesh.visible = false wall_mesh.visible = false
) )
toggle_edit_button.on_button_up.connect(func(): toggle_edit_button.on_button_up.connect(_handle_button_up)
edit_enabled = false
wall_corners.visible = false func _handle_button_up():
wall_edges.visible = false edit_enabled = false
wall_mesh.mesh = generate_mesh()
if wall_mesh.mesh == null: wall_corners.visible = false
return wall_edges.visible = false
wall_mesh.mesh = generate_mesh()
var collisions = generate_collision(wall_mesh.mesh)
if wall_mesh.mesh == null:
return
for old_coll in wall_collisions.get_children(): var collisions = generate_collision(wall_mesh.mesh)
old_coll.queue_free()
for collision in collisions: for old_coll in wall_collisions.get_children():
var static_body = StaticBody3D.new() old_coll.queue_free()
static_body.set_collision_layer_value(4, true)
static_body.set_collision_layer_value(5, true) for collision in collisions:
static_body.collision_mask = 0 var static_body = StaticBody3D.new()
static_body.add_child(collision) static_body.set_collision_layer_value(4, true)
wall_collisions.add_child(static_body) static_body.set_collision_layer_value(5, true)
static_body.collision_mask = 0
wall_mesh.visible = true static_body.add_child(collision)
) wall_collisions.add_child(static_body)
wall_mesh.visible = true
func generate_mesh(): func generate_mesh():
var corner_count = wall_corners.get_child_count() var corner_count = wall_corners.get_child_count()
@ -200,3 +201,17 @@ func corners_to_edge_transform(from_pos: Vector3, to_pos: Vector3) -> Transform3
var edge_transform = Transform3D(edge_basis, edge_position) var edge_transform = Transform3D(edge_basis, edge_position)
return edge_transform return edge_transform
func _save():
return {
"corners": wall_corners.get_children().map(func(corner): return corner.position),
}
func _load(data):
for corner in data["corners"]:
add_corner(corner)
_handle_button_up()
queue_free()

View File

@ -1,6 +1,8 @@
extends Node extends Node
var file_url: String = "user://config.json" const VariantSerializer = preload("res://lib/utils/variant_serializer.gd")
var file_url: String = "user://config.cfg"
func save_config(data: Dictionary): func save_config(data: Dictionary):
var file := FileAccess.open(file_url, FileAccess.WRITE) var file := FileAccess.open(file_url, FileAccess.WRITE)
@ -8,7 +10,7 @@ func save_config(data: Dictionary):
if file == null: if file == null:
return return
var json_data := JSON.stringify(data) var json_data := JSON.stringify(VariantSerializer.stringify_value(data))
file.store_string(json_data) file.store_string(json_data)
func load_config(): func load_config():
@ -18,6 +20,6 @@ func load_config():
return {} return {}
var json_data := file.get_as_text() var json_data := file.get_as_text()
var data = JSON.parse_string(json_data) var data = VariantSerializer.parse_value(JSON.parse_string(json_data))
return data return data

View File

@ -47,6 +47,7 @@ func start_adapter(type: String, url: String, token: String):
add_child(api) add_child(api)
api.on_connect.connect(func(): api.on_connect.connect(func():
SaveSystem.load()
on_connect.emit() on_connect.emit()
) )
@ -92,3 +93,7 @@ func set_state(entity: String, state: String, attributes: Dictionary = {}):
func watch_state(entity: String, callback: Callable): func watch_state(entity: String, callback: Callable):
assert(has_connected(), "Not connected") assert(has_connected(), "Not connected")
return api.watch_state(entity, callback) return api.watch_state(entity, callback)
func _notification(what):
if what == NOTIFICATION_WM_CLOSE_REQUEST || what == NOTIFICATION_WM_GO_BACK_REQUEST:
SaveSystem.save()

View File

@ -0,0 +1,85 @@
extends Node
const VariantSerializer = preload("res://lib/utils/variant_serializer.gd")
func save():
if HomeApi.has_connected() == false:
return
var filename = HomeApi.api.url.split("//")[1].replace("/api/websocket", "").replace(".", "_").replace(":", "_")
var save_file = FileAccess.open("user://%s.save" % filename, FileAccess.WRITE)
if save_file == null:
return
var save_tree = _generate_save_tree(get_tree().root.get_node("Main"))
var json_text = JSON.stringify(save_tree)
save_file.store_line(json_text)
func load():
if HomeApi.has_connected() == false:
return
var filename = HomeApi.api.url.split("//")[1].replace("/api/websocket", "").replace(".", "_").replace(":", "_")
var save_file = FileAccess.open("user://%s.save" % filename, FileAccess.READ)
if save_file == null:
return
var json_text = save_file.get_line()
var save_tree = JSON.parse_string(json_text)
if save_tree is Array:
for tree in save_tree:
_build_save_tree(tree)
else:
_build_save_tree(save_tree)
func _generate_save_tree(node: Node):
var children = []
if node.has_method("_save") == false:
for child in node.get_children():
var data = _generate_save_tree(child)
if data is Array:
for child_data in data:
children.append(child_data)
else:
children.append(data)
return children
var save_tree = {
"data": VariantSerializer.stringify_value(node.call("_save")),
"parent": node.get_parent().get_path(),
"filename": node.get_scene_file_path()
}
for child in node.get_children():
var child_data = _generate_save_tree(child)
if child_data is Array:
for data in child_data:
children.append(data)
else:
children.append(child_data)
save_tree["children"] = children
return save_tree
func _build_save_tree(tree: Dictionary):
var new_object = load(tree["filename"]).instantiate()
get_node(tree["parent"]).add_child(new_object)
if new_object.has_method("_load"):
new_object.call("_load", VariantSerializer.parse_value(tree["data"]))
else:
for key in tree["data"].keys():
new_object.set(key, VariantSerializer.parse_value(tree["data"][key]))

View File

@ -0,0 +1,79 @@
extends Object
static func stringify_value(value):
match typeof(value):
TYPE_DICTIONARY:
var new_dict = {}
for key in value.keys():
new_dict[key] = stringify_value(value[key])
return new_dict
TYPE_ARRAY:
return value.map(func(item):
return stringify_value(item)
)
TYPE_BOOL, TYPE_INT, TYPE_FLOAT, TYPE_STRING:
return value
TYPE_VECTOR2:
return {
"x": value.x,
"y": value.y,
"_type": "Vector2"
}
TYPE_VECTOR3:
return {
"x": value.x,
"y": value.y,
"z": value.z,
"_type": "Vector3"
}
TYPE_TRANSFORM3D:
return {
"origin": stringify_value(value.origin),
"basis": stringify_value(value.basis),
"_type": "Transform3D"
}
TYPE_BASIS:
return {
"x": stringify_value(value.x),
"y": stringify_value(value.y),
"z": stringify_value(value.z),
"_type": "Basis"
}
TYPE_QUATERNION:
return {
"x": value.x,
"y": value.y,
"z": value.z,
"w": value.w,
"_type": "Quaternion"
}
_:
assert(false, "Unsupported type: %s" % typeof(value))
static func parse_value(value):
if typeof(value) == TYPE_ARRAY:
return value.map(func(item):
return parse_value(item)
)
elif typeof(value) == TYPE_DICTIONARY:
if value.has("_type"):
match value["_type"]:
"Vector2":
return Vector2(value["x"], value["y"])
"Vector3":
return Vector3(value["x"], value["y"], value["z"])
"Transform3D":
return Transform3D(parse_value(value["basis"]), parse_value(value["origin"]))
"Basis":
return Basis(parse_value(value["x"]), parse_value(value["y"]), parse_value(value["z"]))
"Quaternion":
return Quaternion(value["x"], value["y"], value["z"], value["w"])
_:
assert(false, "Unsupported type: %s" % value["_type"])
else:
var new_dict = {}
for key in value.keys():
new_dict[key] = parse_value(value[key])
return new_dict
else:
return value

View File

@ -23,6 +23,7 @@ Request="*res://lib/globals/request.gd"
HomeApi="*res://lib/globals/home_api.gd" HomeApi="*res://lib/globals/home_api.gd"
AudioPlayer="*res://lib/globals/audio_player.gd" AudioPlayer="*res://lib/globals/audio_player.gd"
EventSystem="*res://lib/globals/event_system.gd" EventSystem="*res://lib/globals/event_system.gd"
SaveSystem="*res://lib/globals/save_system.gd"
[editor_plugins] [editor_plugins]