105 lines
2.8 KiB
GDScript
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
|