Optimize finalized surface artwork
This commit is contained in:
parent
82938d3d57
commit
f2239d248a
14 changed files with 1254 additions and 305 deletions
|
|
@ -32,7 +32,7 @@ const DRAWING_RELIABLE_CHANNEL: int = 14
|
|||
const FISHING_RELIABLE_CHANNEL: int = 16
|
||||
const HOME_RELIABLE_CHANNEL: int = 17
|
||||
const ENET_CHANNEL_COUNT: int = 18
|
||||
const SURFACE_DRAWING_CAPABILITY: String = "surface_drawing_v2"
|
||||
const SURFACE_DRAWING_CAPABILITY: String = "surface_drawing_v3"
|
||||
const ART_SHOP_CAPABILITY: String = "art_shop_v1"
|
||||
const BACKPACK_SHOP_CAPABILITY: String = "backpack_shop_v1"
|
||||
const WORLD_TIME_CAPABILITY: String = "world_time_v2"
|
||||
|
|
|
|||
|
|
@ -31,6 +31,14 @@ const ARTWORK_DIRECTORY_NAME: String = "artwork"
|
|||
const STAMP_ALPHA_THRESHOLD: float = 0.5
|
||||
const STAMP_COLOR_TOLERANCE: float = 0.012
|
||||
const INVALID_POINTER_SCREEN_POSITION := Vector2(-1.0, -1.0)
|
||||
const SNAPSHOT_CANVASES_PER_FRAME: int = 2
|
||||
# A maximum-size 128 x 128 canvas consumes this entire budget, preventing
|
||||
# late-join synchronization from building two large artwork textures at once.
|
||||
const SNAPSHOT_GRID_CELLS_PER_FRAME: int = 16384
|
||||
const FINALIZED_NODE_LOAD_DISTANCE: float = 108.0
|
||||
const FINALIZED_NODE_UNLOAD_DISTANCE: float = 128.0
|
||||
const FINALIZED_STREAM_INTERVAL_MSEC: int = 250
|
||||
const FINALIZED_NODE_LOADS_PER_PASS: int = 1
|
||||
|
||||
enum GuideAction {
|
||||
NONE,
|
||||
|
|
@ -49,7 +57,10 @@ var _art_unlocks: PlayerArtUnlocks
|
|||
var _data_root: PlayerDataRoot
|
||||
var _drawing_root: Node3D
|
||||
var _canvas_states: Dictionary[String, Dictionary] = {}
|
||||
var _canvas_cells: Dictionary[String, SurfaceDrawingCellBuffer] = {}
|
||||
var _canvas_nodes: Dictionary[String, SurfaceDrawingCanvas] = {}
|
||||
var _snapshot_queues: Dictionary[int, Array] = {}
|
||||
var _last_finalized_stream_msec: int = 0
|
||||
var _selected_canvas_id: String = ""
|
||||
var _hovered_canvas_id: String = ""
|
||||
var _last_hovered_canvas_id: String = ""
|
||||
|
|
@ -753,8 +764,8 @@ func get_canvas_count() -> int:
|
|||
|
||||
func get_painted_cell_count() -> int:
|
||||
var count: int = 0
|
||||
for state: Dictionary in _canvas_states.values():
|
||||
count += (state.get("cells", []) as Array).size()
|
||||
for buffer: SurfaceDrawingCellBuffer in _canvas_cells.values():
|
||||
count += buffer.get_painted_count()
|
||||
return count
|
||||
|
||||
|
||||
|
|
@ -807,7 +818,13 @@ func get_canvas_ids() -> Array[String]:
|
|||
|
||||
|
||||
func get_canvas_state(canvas_id: String) -> Dictionary:
|
||||
return Dictionary(_canvas_states.get(canvas_id, {})).duplicate(true)
|
||||
var state: Dictionary = _canvas_states.get(canvas_id, {})
|
||||
var buffer: SurfaceDrawingCellBuffer = _canvas_cells.get(canvas_id)
|
||||
if state.is_empty() or buffer == null:
|
||||
return {}
|
||||
var result: Dictionary = state.duplicate(true)
|
||||
result["cells"] = buffer.to_cells()
|
||||
return result
|
||||
|
||||
|
||||
func is_stamp_mode() -> bool:
|
||||
|
|
@ -1029,6 +1046,8 @@ func _next_artwork_export_path(directory: String) -> String:
|
|||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
_pump_snapshot_queues()
|
||||
_refresh_finalized_canvas_streaming()
|
||||
if not _active:
|
||||
return
|
||||
if not can_activate():
|
||||
|
|
@ -1313,8 +1332,8 @@ func _handle_canvas_request(peer_id: int, data: Dictionary) -> void:
|
|||
or not _peer_can_place_grid(peer_id, requested_size)
|
||||
or _canvas_states.size() >= SurfaceDrawingProtocol.MAX_CANVASES
|
||||
or _active_canvas_count() >= SurfaceDrawingProtocol.MAX_ACTIVE_CANVASES
|
||||
or _allocated_grid_cells() + requested_size * requested_size
|
||||
> SurfaceDrawingProtocol.MAX_SESSION_GRID_CELLS
|
||||
or _allocated_active_grid_cells() + requested_size * requested_size
|
||||
> SurfaceDrawingProtocol.MAX_ACTIVE_GRID_CELLS
|
||||
):
|
||||
return
|
||||
var avatar: Player = _spawn_service.get_avatar(peer_id)
|
||||
|
|
@ -1400,8 +1419,10 @@ func _handle_stamp_request(peer_id: int, data: Dictionary) -> void:
|
|||
or not _accept_request(peer_id, str(data["request_id"]))
|
||||
or not _peer_can_stamp(peer_id, data)
|
||||
or _canvas_states.size() >= SurfaceDrawingProtocol.MAX_CANVASES
|
||||
or _allocated_grid_cells() + requested_size * requested_size
|
||||
> SurfaceDrawingProtocol.MAX_SESSION_GRID_CELLS
|
||||
or _finalized_canvas_count()
|
||||
>= SurfaceDrawingProtocol.MAX_FINALIZED_CANVASES
|
||||
or _allocated_finalized_grid_cells() + requested_size * requested_size
|
||||
> SurfaceDrawingProtocol.MAX_FINALIZED_GRID_CELLS
|
||||
):
|
||||
return
|
||||
var avatar: Player = _spawn_service.get_avatar(peer_id)
|
||||
|
|
@ -1442,6 +1463,12 @@ func _handle_stamp_request(peer_id: int, data: Dictionary) -> void:
|
|||
var canvas_id: String = "%s-%d" % [
|
||||
_session.get_session_id().left(16), _canvas_sequence,
|
||||
]
|
||||
var stamp_colors: PackedByteArray = data["pixels"]
|
||||
var stamp_authors := PackedInt32Array()
|
||||
stamp_authors.resize(stamp_colors.size())
|
||||
for cell_key: int in stamp_colors.size():
|
||||
if stamp_colors[cell_key] > 0:
|
||||
stamp_authors[cell_key] = 1
|
||||
var state: Dictionary = {
|
||||
"session_id": _session.get_session_id(),
|
||||
"canvas_id": canvas_id,
|
||||
|
|
@ -1459,9 +1486,9 @@ func _handle_stamp_request(peer_id: int, data: Dictionary) -> void:
|
|||
"layer": _canvas_sequence,
|
||||
"creator_fingerprint": record.identity_fingerprint,
|
||||
"participant_fingerprints": [record.identity_fingerprint],
|
||||
"cells": _stamp_cells(
|
||||
data["pixels"], requested_size, record.identity_fingerprint
|
||||
),
|
||||
"cell_encoding": SurfaceDrawingProtocol.CELL_ENCODING,
|
||||
"cell_colors": stamp_colors,
|
||||
"cell_authors": stamp_authors,
|
||||
"stamp": true,
|
||||
}
|
||||
_apply_canvas_state(state)
|
||||
|
|
@ -1469,27 +1496,6 @@ func _handle_stamp_request(peer_id: int, data: Dictionary) -> void:
|
|||
_emit_hud_state("stamp placed • finalized artwork shared with everyone")
|
||||
|
||||
|
||||
func _stamp_cells(
|
||||
pixels: PackedByteArray,
|
||||
grid_size: int,
|
||||
author_fingerprint: String,
|
||||
) -> Array[Dictionary]:
|
||||
var result: Array[Dictionary] = []
|
||||
var palette_ids: Array[StringName] = SurfaceDrawingPalette.get_color_ids()
|
||||
for cell_key: int in range(pixels.size()):
|
||||
var palette_index: int = pixels[cell_key]
|
||||
if palette_index <= 0:
|
||||
continue
|
||||
var cell_y: int = floori(float(cell_key) / float(grid_size))
|
||||
result.append({
|
||||
"x": cell_key % grid_size,
|
||||
"y": cell_y,
|
||||
"color_id": str(palette_ids[palette_index - 1]),
|
||||
"author_fingerprint": author_fingerprint,
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
@rpc(
|
||||
"any_peer",
|
||||
"call_remote",
|
||||
|
|
@ -1529,6 +1535,18 @@ func _handle_guide_request(peer_id: int, data: Dictionary) -> void:
|
|||
var finalized: bool = bool(data["finalized"])
|
||||
if was_finalized:
|
||||
return
|
||||
if (
|
||||
finalized
|
||||
and (
|
||||
_finalized_canvas_count()
|
||||
>= SurfaceDrawingProtocol.MAX_FINALIZED_CANVASES
|
||||
or _allocated_finalized_grid_cells()
|
||||
+ int(state["width"]) * int(state["height"])
|
||||
> SurfaceDrawingProtocol.MAX_FINALIZED_GRID_CELLS
|
||||
)
|
||||
):
|
||||
_emit_hud_state("finished artwork limit reached for this session")
|
||||
return
|
||||
var guide_visible: bool = bool(data["guide_visible"]) and not finalized
|
||||
if (
|
||||
bool(state.get("guide_visible", true)) == guide_visible
|
||||
|
|
@ -1774,18 +1792,23 @@ func _queue_overlapping_finished_mutations(
|
|||
).normalized()
|
||||
if normal.dot(target_normal) < 0.9:
|
||||
continue
|
||||
var canvas: SurfaceDrawingCanvas = _canvas_nodes.get(canvas_id)
|
||||
if (
|
||||
canvas == null
|
||||
or not canvas.contains_world_point(
|
||||
not _state_contains_world_point(
|
||||
state,
|
||||
world_position,
|
||||
SurfaceDrawingCanvas.SURFACE_SAMPLE_DEPTH + 0.05,
|
||||
)
|
||||
):
|
||||
continue
|
||||
var center_cell: Vector2i = canvas.cell_at_world_point(world_position)
|
||||
var center_cell: Vector2i = _state_cell_at_world_point(
|
||||
state, world_position
|
||||
)
|
||||
if center_cell.x < 0:
|
||||
continue
|
||||
var tangent: Vector3 = SurfaceDrawingProtocol.array_to_vector(
|
||||
state["tangent"]
|
||||
).normalized()
|
||||
var bitangent: Vector3 = normal.cross(tangent).normalized()
|
||||
for y_offset: int in range(-1, 2):
|
||||
for x_offset: int in range(-1, 2):
|
||||
var cell := Vector2i(
|
||||
|
|
@ -1794,19 +1817,19 @@ func _queue_overlapping_finished_mutations(
|
|||
)
|
||||
if (
|
||||
cell.x < 0
|
||||
or cell.x >= canvas.grid_width
|
||||
or cell.x >= int(state["width"])
|
||||
or cell.y < 0
|
||||
or cell.y >= canvas.grid_height
|
||||
or cell.y >= int(state["height"])
|
||||
):
|
||||
continue
|
||||
var relative: Vector3 = (
|
||||
canvas.get_cell_world_position(cell.x, cell.y)
|
||||
_state_cell_position(state, cell.x, cell.y)
|
||||
- world_position
|
||||
)
|
||||
if (
|
||||
absf(relative.dot(canvas.get_surface_tangent()))
|
||||
absf(relative.dot(tangent))
|
||||
>= SurfaceDrawingProtocol.CELL_SIZE
|
||||
or absf(relative.dot(canvas.get_surface_bitangent()))
|
||||
or absf(relative.dot(bitangent))
|
||||
>= SurfaceDrawingProtocol.CELL_SIZE
|
||||
or _state_cell(state, cell.x, cell.y).is_empty()
|
||||
):
|
||||
|
|
@ -1861,11 +1884,9 @@ func _publish_cell_mutations(
|
|||
canvas_ids.sort()
|
||||
for canvas_id: String in canvas_ids:
|
||||
var state: Dictionary = _canvas_states.get(canvas_id, {})
|
||||
if state.is_empty():
|
||||
var buffer: SurfaceDrawingCellBuffer = _canvas_cells.get(canvas_id)
|
||||
if state.is_empty() or buffer == null:
|
||||
continue
|
||||
var cells: Dictionary[int, Dictionary] = _cells_by_key(
|
||||
state["cells"], int(state["width"])
|
||||
)
|
||||
var canvas_mutations: Dictionary = mutations[canvas_id]
|
||||
var cell_keys: Array[int] = []
|
||||
for cell_key_value: Variant in canvas_mutations.keys():
|
||||
|
|
@ -1874,18 +1895,11 @@ func _publish_cell_mutations(
|
|||
var edits: Array[Dictionary] = []
|
||||
for cell_key: int in cell_keys:
|
||||
var edit: Dictionary = canvas_mutations[cell_key]
|
||||
if str(edit["color_id"]).is_empty():
|
||||
cells.erase(cell_key)
|
||||
else:
|
||||
cells[cell_key] = edit.duplicate(true)
|
||||
edits.append(edit.duplicate(true))
|
||||
if edits.is_empty():
|
||||
if edits.is_empty() or not buffer.apply_edits(edits):
|
||||
continue
|
||||
state["revision"] = int(state["revision"]) + 1
|
||||
state["cells"] = _sorted_cells(cells)
|
||||
state["participant_fingerprints"] = _merged_participants(
|
||||
state, {"edits": edits}
|
||||
)
|
||||
state["participant_fingerprints"] = buffer.get_participants()
|
||||
_canvas_states[canvas_id] = state
|
||||
var update: Dictionary = {
|
||||
"session_id": _session.get_session_id(),
|
||||
|
|
@ -1898,7 +1912,7 @@ func _publish_cell_mutations(
|
|||
}
|
||||
var local_canvas: SurfaceDrawingCanvas = _canvas_nodes.get(canvas_id)
|
||||
if local_canvas != null:
|
||||
local_canvas.apply_update(update)
|
||||
local_canvas.apply_buffered_update(update)
|
||||
_broadcast_canvas_update(update)
|
||||
_emit_artwork_changed()
|
||||
|
||||
|
|
@ -2068,8 +2082,13 @@ func request_canvas_snapshot(request_id: String) -> void:
|
|||
)
|
||||
):
|
||||
return
|
||||
for state: Dictionary in _canvas_states.values():
|
||||
receive_canvas_state.rpc_id(sender_id, state)
|
||||
var canvas_ids: Array[String] = get_canvas_ids()
|
||||
canvas_ids.sort_custom(func(first: String, second: String) -> bool:
|
||||
return _snapshot_canvas_precedes(first, second, sender_id)
|
||||
)
|
||||
var queued: Array = []
|
||||
queued.assign(canvas_ids)
|
||||
_snapshot_queues[sender_id] = queued
|
||||
|
||||
|
||||
func _apply_canvas_state(data: Dictionary) -> void:
|
||||
|
|
@ -2085,12 +2104,21 @@ func _apply_canvas_state(data: Dictionary) -> void:
|
|||
and int(data["revision"]) <= int(previous_state["revision"])
|
||||
):
|
||||
return
|
||||
var buffer := SurfaceDrawingCellBuffer.new()
|
||||
if not buffer.configure_from_state(data, true):
|
||||
return
|
||||
var normalized_state: Dictionary = data.duplicate(true)
|
||||
normalized_state["participant_fingerprints"] = _state_participants(
|
||||
normalized_state
|
||||
)
|
||||
normalized_state.erase("cells")
|
||||
normalized_state.erase("cell_encoding")
|
||||
normalized_state.erase("cell_colors")
|
||||
normalized_state.erase("cell_authors")
|
||||
normalized_state["participant_fingerprints"] = buffer.get_participants()
|
||||
_canvas_states[canvas_id] = normalized_state
|
||||
_rebuild_canvas_node(normalized_state)
|
||||
_canvas_cells[canvas_id] = buffer
|
||||
if _should_load_canvas_node(normalized_state, FINALIZED_NODE_LOAD_DISTANCE):
|
||||
_rebuild_canvas_node(normalized_state, buffer)
|
||||
else:
|
||||
_unload_canvas_node(canvas_id)
|
||||
_refresh_stencil_visibility()
|
||||
_emit_artwork_changed()
|
||||
if (
|
||||
|
|
@ -2105,7 +2133,10 @@ func _apply_canvas_state(data: Dictionary) -> void:
|
|||
)
|
||||
|
||||
|
||||
func _rebuild_canvas_node(data: Dictionary) -> void:
|
||||
func _rebuild_canvas_node(
|
||||
data: Dictionary,
|
||||
buffer: SurfaceDrawingCellBuffer,
|
||||
) -> void:
|
||||
var canvas_id: String = str(data["canvas_id"])
|
||||
var previous_canvas: SurfaceDrawingCanvas = _canvas_nodes.get(canvas_id)
|
||||
if previous_canvas != null:
|
||||
|
|
@ -2113,12 +2144,82 @@ func _rebuild_canvas_node(data: Dictionary) -> void:
|
|||
var canvas := SurfaceDrawingCanvas.new()
|
||||
canvas.name = "Drawing_%s" % canvas_id
|
||||
_drawing_root.add_child(canvas)
|
||||
if not canvas.setup(data, _relationships, SOLID_SURFACE_MASK):
|
||||
if not canvas.setup(data, _relationships, SOLID_SURFACE_MASK, buffer):
|
||||
canvas.queue_free()
|
||||
return
|
||||
_canvas_nodes[canvas_id] = canvas
|
||||
|
||||
|
||||
func _refresh_finalized_canvas_streaming() -> void:
|
||||
var now: int = Time.get_ticks_msec()
|
||||
if now - _last_finalized_stream_msec < FINALIZED_STREAM_INTERVAL_MSEC:
|
||||
return
|
||||
_last_finalized_stream_msec = now
|
||||
for canvas_id: String in _canvas_nodes.keys():
|
||||
var state: Dictionary = _canvas_states.get(canvas_id, {})
|
||||
if (
|
||||
bool(state.get("finalized", false))
|
||||
and not _should_load_canvas_node(
|
||||
state, FINALIZED_NODE_UNLOAD_DISTANCE
|
||||
)
|
||||
):
|
||||
_unload_canvas_node(canvas_id)
|
||||
var candidates: Array[String] = []
|
||||
for canvas_id: String in _canvas_states:
|
||||
var state: Dictionary = _canvas_states[canvas_id]
|
||||
if (
|
||||
not _canvas_nodes.has(canvas_id)
|
||||
and _should_load_canvas_node(state, FINALIZED_NODE_LOAD_DISTANCE)
|
||||
):
|
||||
candidates.append(canvas_id)
|
||||
candidates.sort_custom(func(first: String, second: String) -> bool:
|
||||
return _canvas_distance_squared(first) < _canvas_distance_squared(second)
|
||||
)
|
||||
for index: int in mini(candidates.size(), FINALIZED_NODE_LOADS_PER_PASS):
|
||||
var canvas_id: String = candidates[index]
|
||||
var buffer: SurfaceDrawingCellBuffer = _canvas_cells.get(canvas_id)
|
||||
if buffer != null:
|
||||
_rebuild_canvas_node(_canvas_states[canvas_id], buffer)
|
||||
|
||||
|
||||
func _should_load_canvas_node(state: Dictionary, distance: float) -> bool:
|
||||
if state.is_empty():
|
||||
return false
|
||||
if not bool(state.get("finalized", false)):
|
||||
return true
|
||||
if _local_player == null or not is_instance_valid(_local_player):
|
||||
return false
|
||||
return _local_player.global_position.distance_squared_to(
|
||||
SurfaceDrawingProtocol.array_to_vector(state["origin"])
|
||||
) <= distance * distance
|
||||
|
||||
|
||||
func _canvas_distance_squared(canvas_id: String) -> float:
|
||||
var state: Dictionary = _canvas_states.get(canvas_id, {})
|
||||
if (
|
||||
state.is_empty()
|
||||
or _local_player == null
|
||||
or not is_instance_valid(_local_player)
|
||||
):
|
||||
return INF
|
||||
return _local_player.global_position.distance_squared_to(
|
||||
SurfaceDrawingProtocol.array_to_vector(state["origin"])
|
||||
)
|
||||
|
||||
|
||||
func _unload_canvas_node(canvas_id: String) -> void:
|
||||
var canvas: SurfaceDrawingCanvas = _canvas_nodes.get(canvas_id)
|
||||
if canvas != null:
|
||||
canvas.queue_free()
|
||||
_canvas_nodes.erase(canvas_id)
|
||||
if _selected_canvas_id == canvas_id:
|
||||
_selected_canvas_id = ""
|
||||
if _hovered_canvas_id == canvas_id:
|
||||
_hovered_canvas_id = ""
|
||||
if _last_hovered_canvas_id == canvas_id:
|
||||
_last_hovered_canvas_id = ""
|
||||
|
||||
|
||||
func _apply_canvas_update(data: Dictionary) -> void:
|
||||
if (
|
||||
not SurfaceDrawingProtocol.validate_canvas_update(data)
|
||||
|
|
@ -2127,12 +2228,14 @@ func _apply_canvas_update(data: Dictionary) -> void:
|
|||
return
|
||||
var canvas_id: String = str(data["canvas_id"])
|
||||
var state: Dictionary = _canvas_states.get(canvas_id, {})
|
||||
var buffer: SurfaceDrawingCellBuffer = _canvas_cells.get(canvas_id)
|
||||
var canvas: SurfaceDrawingCanvas = _canvas_nodes.get(canvas_id)
|
||||
if state.is_empty() or canvas == null or int(data["revision"]) <= int(state["revision"]):
|
||||
if (
|
||||
state.is_empty()
|
||||
or buffer == null
|
||||
or int(data["revision"]) <= int(state["revision"])
|
||||
):
|
||||
return
|
||||
var cells: Dictionary[int, Dictionary] = _cells_by_key(
|
||||
state["cells"], int(state["width"])
|
||||
)
|
||||
var grid_width: int = int(state["width"])
|
||||
var grid_height: int = int(state["height"])
|
||||
for edit_value: Variant in data["edits"]:
|
||||
|
|
@ -2141,18 +2244,13 @@ func _apply_canvas_update(data: Dictionary) -> void:
|
|||
var y: int = int(edit["y"])
|
||||
if x < 0 or x >= grid_width or y < 0 or y >= grid_height:
|
||||
return
|
||||
var key: int = y * grid_width + x
|
||||
if str(edit["color_id"]).is_empty():
|
||||
cells.erase(key)
|
||||
else:
|
||||
cells[key] = edit.duplicate(true)
|
||||
if not buffer.apply_edits(data["edits"]):
|
||||
return
|
||||
state["revision"] = int(data["revision"])
|
||||
state["cells"] = _sorted_cells(cells)
|
||||
state["participant_fingerprints"] = _merged_participants(
|
||||
state, data
|
||||
)
|
||||
state["participant_fingerprints"] = buffer.get_participants()
|
||||
_canvas_states[canvas_id] = state
|
||||
canvas.apply_update(data)
|
||||
if canvas != null:
|
||||
canvas.apply_buffered_update(data)
|
||||
_emit_artwork_changed()
|
||||
|
||||
|
||||
|
|
@ -2167,7 +2265,6 @@ func _apply_guide_update(data: Dictionary) -> void:
|
|||
var canvas: SurfaceDrawingCanvas = _canvas_nodes.get(canvas_id)
|
||||
if (
|
||||
state.is_empty()
|
||||
or canvas == null
|
||||
or int(data["revision"]) <= int(state["revision"])
|
||||
):
|
||||
return
|
||||
|
|
@ -2179,8 +2276,13 @@ func _apply_guide_update(data: Dictionary) -> void:
|
|||
state["participant_fingerprints"] = _merged_participants(
|
||||
state, data
|
||||
)
|
||||
var buffer: SurfaceDrawingCellBuffer = _canvas_cells.get(canvas_id)
|
||||
if buffer != null:
|
||||
buffer.ensure_participants(state["participant_fingerprints"])
|
||||
state["participant_fingerprints"] = buffer.get_participants()
|
||||
_canvas_states[canvas_id] = state
|
||||
canvas.apply_guide_update(data)
|
||||
if canvas != null:
|
||||
canvas.apply_guide_update(data)
|
||||
if _active:
|
||||
_emit_hud_state(
|
||||
"grid finalized • new grids may overlap its artwork"
|
||||
|
|
@ -2193,13 +2295,90 @@ func _apply_guide_update(data: Dictionary) -> void:
|
|||
|
||||
|
||||
func _broadcast_canvas_state(data: Dictionary) -> void:
|
||||
var network_state: Dictionary = _network_canvas_state(
|
||||
str(data.get("canvas_id", ""))
|
||||
)
|
||||
if network_state.is_empty():
|
||||
return
|
||||
for peer_id: int in _session.get_authenticated_peer_ids():
|
||||
if peer_id == _session.get_local_peer_id():
|
||||
continue
|
||||
if _session.peer_supports_capability(
|
||||
peer_id, SurfaceDrawingProtocol.CAPABILITY
|
||||
):
|
||||
receive_canvas_state.rpc_id(peer_id, data)
|
||||
receive_canvas_state.rpc_id(peer_id, network_state)
|
||||
|
||||
|
||||
func _network_canvas_state(canvas_id: String) -> Dictionary:
|
||||
var state: Dictionary = _canvas_states.get(canvas_id, {})
|
||||
var buffer: SurfaceDrawingCellBuffer = _canvas_cells.get(canvas_id)
|
||||
if state.is_empty() or buffer == null:
|
||||
return {}
|
||||
var result: Dictionary = state.duplicate(true)
|
||||
result.merge(buffer.to_compact_fields(), true)
|
||||
return result
|
||||
|
||||
|
||||
func _pump_snapshot_queues() -> void:
|
||||
if _session == null or not _session.is_host() or _snapshot_queues.is_empty():
|
||||
return
|
||||
var peer_ids: Array[int] = []
|
||||
peer_ids.assign(_snapshot_queues.keys())
|
||||
peer_ids.sort()
|
||||
for peer_id: int in peer_ids:
|
||||
if not _session.is_authenticated_peer(peer_id):
|
||||
_snapshot_queues.erase(peer_id)
|
||||
continue
|
||||
var queue: Array = _snapshot_queues.get(peer_id, [])
|
||||
var sent_count: int = 0
|
||||
var sent_grid_cells: int = 0
|
||||
while not queue.is_empty() and sent_count < SNAPSHOT_CANVASES_PER_FRAME:
|
||||
var canvas_id: String = str(queue.front())
|
||||
var state: Dictionary = _canvas_states.get(canvas_id, {})
|
||||
if state.is_empty():
|
||||
queue.pop_front()
|
||||
continue
|
||||
var grid_cells: int = int(state["width"]) * int(state["height"])
|
||||
if (
|
||||
sent_count > 0
|
||||
and sent_grid_cells + grid_cells > SNAPSHOT_GRID_CELLS_PER_FRAME
|
||||
):
|
||||
break
|
||||
queue.pop_front()
|
||||
var network_state: Dictionary = _network_canvas_state(canvas_id)
|
||||
if network_state.is_empty():
|
||||
continue
|
||||
receive_canvas_state.rpc_id(peer_id, network_state)
|
||||
sent_count += 1
|
||||
sent_grid_cells += grid_cells
|
||||
if queue.is_empty():
|
||||
_snapshot_queues.erase(peer_id)
|
||||
else:
|
||||
_snapshot_queues[peer_id] = queue
|
||||
|
||||
|
||||
func _snapshot_canvas_precedes(
|
||||
first_id: String,
|
||||
second_id: String,
|
||||
peer_id: int,
|
||||
) -> bool:
|
||||
var first: Dictionary = _canvas_states.get(first_id, {})
|
||||
var second: Dictionary = _canvas_states.get(second_id, {})
|
||||
var first_finalized: bool = bool(first.get("finalized", false))
|
||||
var second_finalized: bool = bool(second.get("finalized", false))
|
||||
if first_finalized != second_finalized:
|
||||
return not first_finalized
|
||||
var avatar: Player = _spawn_service.get_avatar(peer_id)
|
||||
if avatar != null:
|
||||
var first_distance: float = avatar.global_position.distance_squared_to(
|
||||
SurfaceDrawingProtocol.array_to_vector(first.get("origin", []))
|
||||
)
|
||||
var second_distance: float = avatar.global_position.distance_squared_to(
|
||||
SurfaceDrawingProtocol.array_to_vector(second.get("origin", []))
|
||||
)
|
||||
if not is_equal_approx(first_distance, second_distance):
|
||||
return first_distance < second_distance
|
||||
return int(first.get("layer", 0)) < int(second.get("layer", 0))
|
||||
|
||||
|
||||
func _broadcast_canvas_update(data: Dictionary) -> void:
|
||||
|
|
@ -2285,6 +2464,61 @@ func _state_cell_position(state: Dictionary, x: int, y: int) -> Vector3:
|
|||
)
|
||||
|
||||
|
||||
func _state_contains_world_point(
|
||||
state: Dictionary,
|
||||
world_point: Vector3,
|
||||
tolerance: float = 0.3,
|
||||
) -> bool:
|
||||
var origin: Vector3 = SurfaceDrawingProtocol.array_to_vector(state["origin"])
|
||||
var normal: Vector3 = SurfaceDrawingProtocol.array_to_vector(
|
||||
state["normal"]
|
||||
).normalized()
|
||||
var relative: Vector3 = world_point - origin
|
||||
if absf(relative.dot(normal)) > tolerance:
|
||||
return false
|
||||
var tangent: Vector3 = SurfaceDrawingProtocol.array_to_vector(
|
||||
state["tangent"]
|
||||
).normalized()
|
||||
var bitangent: Vector3 = normal.cross(tangent).normalized()
|
||||
var half_width: float = (
|
||||
float(state["width"]) * float(state["cell_size"]) * 0.5
|
||||
)
|
||||
var half_height: float = (
|
||||
float(state["height"]) * float(state["cell_size"]) * 0.5
|
||||
)
|
||||
var horizontal: float = relative.dot(tangent)
|
||||
var vertical: float = relative.dot(bitangent)
|
||||
return (
|
||||
horizontal >= -half_width
|
||||
and horizontal < half_width
|
||||
and vertical >= -half_height
|
||||
and vertical < half_height
|
||||
)
|
||||
|
||||
|
||||
func _state_cell_at_world_point(
|
||||
state: Dictionary,
|
||||
world_point: Vector3,
|
||||
) -> Vector2i:
|
||||
var origin: Vector3 = SurfaceDrawingProtocol.array_to_vector(state["origin"])
|
||||
var normal: Vector3 = SurfaceDrawingProtocol.array_to_vector(
|
||||
state["normal"]
|
||||
).normalized()
|
||||
var tangent: Vector3 = SurfaceDrawingProtocol.array_to_vector(
|
||||
state["tangent"]
|
||||
).normalized()
|
||||
var bitangent: Vector3 = normal.cross(tangent).normalized()
|
||||
var relative: Vector3 = world_point - origin
|
||||
var size: float = float(state["cell_size"])
|
||||
var half_width: float = float(state["width"]) * size * 0.5
|
||||
var half_height: float = float(state["height"]) * size * 0.5
|
||||
var x: int = floori((relative.dot(tangent) + half_width) / size)
|
||||
var y: int = floori((relative.dot(bitangent) + half_height) / size)
|
||||
if x < 0 or x >= int(state["width"]) or y < 0 or y >= int(state["height"]):
|
||||
return Vector2i(-1, -1)
|
||||
return Vector2i(x, y)
|
||||
|
||||
|
||||
func _overlaps_existing_canvas(
|
||||
origin: Vector3,
|
||||
normal: Vector3,
|
||||
|
|
@ -2335,10 +2569,27 @@ func _active_canvas_count() -> int:
|
|||
return count
|
||||
|
||||
|
||||
func _allocated_grid_cells() -> int:
|
||||
func _finalized_canvas_count() -> int:
|
||||
var count: int = 0
|
||||
for state: Dictionary in _canvas_states.values():
|
||||
count += int(state.get("width", 0)) * int(state.get("height", 0))
|
||||
if bool(state.get("finalized", false)):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
func _allocated_active_grid_cells() -> int:
|
||||
var count: int = 0
|
||||
for state: Dictionary in _canvas_states.values():
|
||||
if not bool(state.get("finalized", false)):
|
||||
count += int(state.get("width", 0)) * int(state.get("height", 0))
|
||||
return count
|
||||
|
||||
|
||||
func _allocated_finalized_grid_cells() -> int:
|
||||
var count: int = 0
|
||||
for state: Dictionary in _canvas_states.values():
|
||||
if bool(state.get("finalized", false)):
|
||||
count += int(state.get("width", 0)) * int(state.get("height", 0))
|
||||
return count
|
||||
|
||||
|
||||
|
|
@ -2678,30 +2929,10 @@ func _new_request_id(prefix: String) -> String:
|
|||
return "%s-%d-%d" % [prefix, Time.get_ticks_msec(), _request_sequence]
|
||||
|
||||
|
||||
func _cells_by_key(
|
||||
values: Array,
|
||||
grid_width: int,
|
||||
) -> Dictionary[int, Dictionary]:
|
||||
var result: Dictionary[int, Dictionary] = {}
|
||||
for value: Variant in values:
|
||||
if typeof(value) != TYPE_DICTIONARY:
|
||||
continue
|
||||
var cell: Dictionary = value
|
||||
var key: int = (
|
||||
int(cell.get("y", -1)) * grid_width
|
||||
+ int(cell.get("x", -1))
|
||||
)
|
||||
result[key] = cell.duplicate(true)
|
||||
return result
|
||||
|
||||
|
||||
func _state_cell(state: Dictionary, x: int, y: int) -> Dictionary:
|
||||
var cells: Dictionary[int, Dictionary] = _cells_by_key(
|
||||
state.get("cells", []), int(state["width"])
|
||||
)
|
||||
return Dictionary(
|
||||
cells.get(_cell_key_for_state(state, x, y), {})
|
||||
).duplicate(true)
|
||||
var canvas_id: String = str(state.get("canvas_id", ""))
|
||||
var buffer: SurfaceDrawingCellBuffer = _canvas_cells.get(canvas_id)
|
||||
return buffer.get_cell_value(x, y) if buffer != null else {}
|
||||
|
||||
|
||||
func _register_canvas_participant(
|
||||
|
|
@ -2721,6 +2952,9 @@ func _register_canvas_participant(
|
|||
participants.append(fingerprint)
|
||||
state["participant_fingerprints"] = participants
|
||||
_canvas_states[canvas_id] = state
|
||||
var buffer: SurfaceDrawingCellBuffer = _canvas_cells.get(canvas_id)
|
||||
if buffer != null:
|
||||
buffer.ensure_participants(participants)
|
||||
return true
|
||||
|
||||
|
||||
|
|
@ -2740,17 +2974,17 @@ func _merged_participants(
|
|||
|
||||
|
||||
func _state_participants(state: Dictionary) -> Array[String]:
|
||||
var buffer: SurfaceDrawingCellBuffer = _canvas_cells.get(
|
||||
str(state.get("canvas_id", ""))
|
||||
)
|
||||
if buffer != null:
|
||||
return buffer.get_participants()
|
||||
var result: Array[String] = []
|
||||
_append_participant(
|
||||
result, str(state.get("creator_fingerprint", ""))
|
||||
)
|
||||
for value: Variant in state.get("participant_fingerprints", []):
|
||||
_append_participant(result, str(value))
|
||||
for cell_value: Variant in state.get("cells", []):
|
||||
var cell: Dictionary = cell_value
|
||||
_append_participant(
|
||||
result, str(cell.get("author_fingerprint", ""))
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
|
|
@ -2804,15 +3038,6 @@ func _mutation_key(canvas_id: String, x: int, y: int) -> String:
|
|||
return "%s:%d:%d" % [canvas_id, x, y]
|
||||
|
||||
|
||||
func _sorted_cells(values: Dictionary[int, Dictionary]) -> Array[Dictionary]:
|
||||
var keys: Array[int] = values.keys()
|
||||
keys.sort()
|
||||
var result: Array[Dictionary] = []
|
||||
for key: int in keys:
|
||||
result.append(values[key].duplicate(true))
|
||||
return result
|
||||
|
||||
|
||||
func _normalized_or(value: Variant, fallback: Vector3) -> Vector3:
|
||||
var vector: Vector3 = value if typeof(value) == TYPE_VECTOR3 else fallback
|
||||
return fallback if vector.is_zero_approx() else vector.normalized()
|
||||
|
|
@ -2850,6 +3075,7 @@ func _on_peer_removed(peer_id: int) -> void:
|
|||
_peer_request_ids.erase(peer_id)
|
||||
_stroke_history_by_peer.erase(peer_id)
|
||||
_peer_art_entitlements.erase(peer_id)
|
||||
_snapshot_queues.erase(peer_id)
|
||||
|
||||
|
||||
func _on_local_art_entitlement_changed() -> void:
|
||||
|
|
@ -2898,6 +3124,7 @@ func _on_session_state_changed(state: NetworkSession.State) -> void:
|
|||
|
||||
func _clear_session_artwork_state() -> void:
|
||||
_canvas_states.clear()
|
||||
_canvas_cells.clear()
|
||||
for canvas: SurfaceDrawingCanvas in _canvas_nodes.values():
|
||||
canvas.queue_free()
|
||||
_canvas_nodes.clear()
|
||||
|
|
@ -2908,6 +3135,7 @@ func _clear_session_artwork_state() -> void:
|
|||
_stroke_history_by_peer.clear()
|
||||
_cell_last_stroke.clear()
|
||||
_last_local_stroke_id = ""
|
||||
_snapshot_queues.clear()
|
||||
_hide_previews()
|
||||
_emit_artwork_changed()
|
||||
|
||||
|
|
@ -2932,16 +3160,8 @@ func _apply_artwork_fingerprint_clear(fingerprint: String) -> int:
|
|||
|
||||
func _remove_canvas_state(canvas_id: String) -> void:
|
||||
_canvas_states.erase(canvas_id)
|
||||
var canvas: SurfaceDrawingCanvas = _canvas_nodes.get(canvas_id)
|
||||
if canvas != null:
|
||||
canvas.queue_free()
|
||||
_canvas_nodes.erase(canvas_id)
|
||||
if _selected_canvas_id == canvas_id:
|
||||
_selected_canvas_id = ""
|
||||
if _hovered_canvas_id == canvas_id:
|
||||
_hovered_canvas_id = ""
|
||||
if _last_hovered_canvas_id == canvas_id:
|
||||
_last_hovered_canvas_id = ""
|
||||
_canvas_cells.erase(canvas_id)
|
||||
_unload_canvas_node(canvas_id)
|
||||
|
||||
|
||||
func _emit_artwork_changed() -> void:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue