Files
tekton/addons/godot_ai/utils/editor_log_buffer.gd
T

105 lines
2.8 KiB
GDScript

@tool
class_name McpEditorLogBuffer
extends McpStructuredLogRing
## Ring buffer for editor-process script errors and warnings (parse errors,
## @tool runtime errors, EditorPlugin errors, push_error/push_warning) captured
## by editor_logger.gd's Logger subclass.
##
## Smaller cap than McpGameLogBuffer (500 vs 2000) — the editor only emits errors,
## not the full println firehose a game can produce. No run_id rotation: editor
## errors persist across project_run cycles (they're about *editing* state, not
## about the playing game).
##
## Mutex-protected because Logger virtuals can fire from any thread (e.g.
## async script-loader threads emitting parse errors), and the buffer is
## read on the main thread by EditorHandler.get_logs. Each public method
## wraps the base ring's lockless helpers in `_mutex.lock()/unlock()` —
## the base stays lockless so McpGameLogBuffer's hot path doesn't pay an
## unused mutex cost.
##
## Entry shape: {source: "editor", level: "info"|"warn"|"error",
## text, path, line, function} — `path/line/function` may be empty/zero
## when the source location wasn't recoverable (e.g. printerr from a
## thread without a script context).
const MAX_LINES := 500
var _mutex := Mutex.new()
func _init() -> void:
super._init(MAX_LINES)
func append(level: String, text: String, path: String = "", line: int = 0, function: String = "", details: Dictionary = {}) -> void:
var entry := {
"source": "editor",
"level": _coerce_level(level),
"text": text,
"path": path,
"line": line,
"function": function,
}
if not details.is_empty():
entry["details"] = details.duplicate(true)
_mutex.lock()
_append_entry(entry)
_mutex.unlock()
func get_range(offset: int, count: int) -> Array[Dictionary]:
_mutex.lock()
var out := _get_range_unlocked(offset, count)
_mutex.unlock()
return out
func get_recent(count: int) -> Array[Dictionary]:
## Single-lock so the size we compute `start` from can't race against
## a concurrent append between the size read and the slice copy.
_mutex.lock()
var size := _total_count_unlocked()
var start := maxi(0, size - count)
var out := _get_range_unlocked(start, size - start)
_mutex.unlock()
return out
func get_since(since_seq: int, limit: int = -1) -> Dictionary:
## Single-lock so the cursor snapshot and slice copy can't race against a
## Logger-thread append.
_mutex.lock()
var out := _get_since_unlocked(since_seq, limit)
_mutex.unlock()
return out
func total_count() -> int:
_mutex.lock()
var n := _total_count_unlocked()
_mutex.unlock()
return n
func dropped_count() -> int:
_mutex.lock()
var n := _dropped_count_unlocked()
_mutex.unlock()
return n
func appended_total() -> int:
_mutex.lock()
var n := _appended_total_unlocked()
_mutex.unlock()
return n
func clear() -> int:
_mutex.lock()
var n := _total_count_unlocked()
_clear_storage()
_mutex.unlock()
return n