# Action Completion and Callback System Design ## Overview This document details the completion and callback system that allows actions to notify the CutsceneManager when they have finished executing, and enables flexible callback mechanisms for complex action sequences. ## Signal-Based Completion System ### Core Signals Each action implements these core signals: ```gdscript # Emitted when the action starts executing signal started() # Emitted when the action completes successfully signal completed() # Emitted when the action fails signal failed(error_message) # Emitted when the action is manually stopped signal stopped() ``` ### Signal Connection Flow 1. CutsceneManager creates an action 2. CutsceneManager connects to the action's signals 3. When action starts, it emits `started()` 4. When action completes, it emits `completed()` 5. CutsceneManager's signal handlers update the cutscene state ```gdscript # In CutsceneManager func add_action(action: Action) -> void: # Connect to action signals action.connect("started", _on_action_started) action.connect("completed", _on_action_completed) action.connect("failed", _on_action_failed) action.connect("stopped", _on_action_stopped) # Add to queue sequential_actions.append(action) func _on_action_started(action: Action) -> void: emit_signal("action_started", action) func _on_action_completed(action: Action) -> void: # Handle action completion # Move to next action or complete cutscene _handle_action_completion(action) func _on_action_failed(action: Action, error_message: String) -> void: # Handle action failure print("Action failed: %s - %s" % [action.name, error_message]) # Stop the cutscene or handle error appropriately stop() func _on_action_stopped(action: Action) -> void: # Handle action being stopped _handle_action_completion(action) ``` ## Parallel Action Group Completion For parallel actions, the system tracks completion of all actions in a group: ```gdscript # Structure for tracking parallel action groups { "actions": [], # Array of actions in this group "completed_count": 0, # Number of completed actions "total_count": 0, # Total number of actions "on_complete": Callable # Optional callback when group completes } func add_parallel_actions(actions: Array, on_complete: Callable = Callable()) -> void: # Create a parallel action group var group = { "actions": actions, "completed_count": 0, "total_count": actions.size(), "on_complete": on_complete } # Connect to each action's completed signal for action in actions: action.connect("completed", _on_parallel_action_completed.bind(group, action)) action.connect("failed", _on_parallel_action_failed.bind(group, action)) # Add to active parallel groups active_parallel_groups.append(group) # Add to sequential queue as a single item sequential_actions.append(group) func _on_parallel_action_completed(group: Dictionary, action: Action) -> void: # Increment completed count group["completed_count"] += 1 # Check if all actions in group are completed if group["completed_count"] >= group["total_count"]: # Remove from active groups active_parallel_groups.erase(group) # Call optional completion callback if group["on_complete"].is_valid(): group["on_complete"].call() # Continue with next sequential action _execute_next_action() ``` ## Callback System ### Action-Level Callbacks Actions can have callbacks for specific events: ```gdscript class_name Action extends RefCounted # Callback properties var on_started: Callable = Callable() var on_completed: Callable = Callable() var on_failed: Callable = Callable() func start() -> void: ._set_running() # Call started callback if valid if on_started.is_valid(): on_started.call(self) func _set_completed() -> void: state = State.COMPLETED completed.emit() # Call completed callback if valid if on_completed.is_valid(): on_completed.call(self) func _set_failed(error_message: String) -> void: state = State.FAILED failed.emit(error_message) # Call failed callback if valid if on_failed.is_valid(): on_failed.call(self, error_message) ``` ### Cutscene-Level Callbacks The CutsceneManager supports callbacks for cutscene events: ```gdscript class_name CutsceneManager extends Node # Cutscene callbacks var on_started: Callable = Callable() var on_completed: Callable = Callable() var on_paused: Callable = Callable() var on_resumed: Callable = Callable() func start() -> void: if state != State.IDLE: return state = State.RUNNING is_active = true emit_signal("cutscene_started") # Call started callback if on_started.is_valid(): on_started.call() # Start first action _execute_next_action() func _on_cutscene_completed() -> void: state = State.IDLE is_active = false emit_signal("cutscene_completed") # Call completed callback if on_completed.is_valid(): on_completed.call() ``` ## Chaining Actions with Callbacks Actions can be chained together using callbacks: ```gdscript # Example: Create a sequence using callbacks func create_chained_sequence() -> void: var action1 = MoveAction.new(character1, Vector2(100, 100)) var action2 = DialogueAction.new(character1, "I have arrived!") var action3 = AnimationAction.new(character1, "celebrate") # Chain actions using callbacks action1.on_completed = func(action): _execute_action(action2) action2.on_completed = func(action): _execute_action(action3) # Start the sequence _execute_action(action1) ``` ## Error Handling and Recovery The system includes robust error handling: ```gdscript # Action failure handling func _on_action_failed(action: Action, error_message: String) -> void: print("Action failed: %s - %s" % [action.name, error_message]) # Emit a general failure signal emit_signal("action_failed", action, error_message) # Decide how to handle the failure: # 1. Stop the entire cutscene # 2. Skip the failed action and continue # 3. Retry the action # 4. Execute an alternative action # For now, we'll stop the cutscene stop() # Optional retry mechanism func _retry_action(action: Action, max_retries: int = 3) -> void: if not action.has_method("retry_count"): action.retry_count = 0 action.retry_count += 1 if action.retry_count <= max_retries: print("Retrying action: %s (attempt %d)" % [action.name, action.retry_count]) action.start() else: print("Max retries exceeded for action: %s" % action.name) _on_action_failed(action, "Max retries exceeded") ``` ## Integration with Godot's Signal System The completion system fully integrates with Godot's signal system: 1. Uses Godot's built-in signal connection mechanisms 2. Supports both method and callable-based connections 3. Handles disconnection automatically when actions are removed 4. Provides type safety through signal parameters ```gdscript # Example of connecting to cutscene signals from outside func setup_cutscene() -> void: var cutscene = CutsceneManager.new() # Connect to cutscene signals cutscene.connect("cutscene_started", _on_cutscene_started) cutscene.connect("cutscene_completed", _on_cutscene_completed) cutscene.connect("action_started", _on_action_started) cutscene.connect("action_completed", _on_action_completed) # Configure callbacks cutscene.on_completed = func(): print("Cutscene finished, returning to gameplay") # Return to gameplay state return cutscene ``` This completion and callback system provides a flexible, robust foundation for managing action completion in cutscenes while maintaining clean separation of concerns and enabling complex action sequences.