Files
experiment-adventure-ai/action_completion_system.md
2025-07-30 21:52:27 -07:00

275 lines
8.0 KiB
Markdown

# 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.