feat: performance budget profiling setup
- docs/performance-budget.md: 60fps/2GB VRAM budget with CPU, GPU, memory, and draw-call targets (P95<16.6ms, P99<50ms) - scripts/profiling/: SceneTree + Node profilers, test scenes - scripts/generate_occluders.gd: auto-generate OccluderInstance3D from CSG wall pieces (128^3 voxel occlusion culling) - scripts/convert_csg_to_mesh.gd: CSG->StaticMesh baker with LOD slots (LOD1@15m, LOD2@30m) - project.godot: occlusion culling + LOD settings enabled - modular scene UV2 data from lightmapping pass - rcon_command_handler.gd conflict resolved (kept upstream plugin cmd) - Fixed profiler_node.gd warmup frame bug (60 frames, not 3) - Fixed convert_csg_to_mesh.gd LOD API (add_lod + distance)
This commit is contained in:
@@ -60,7 +60,7 @@ func handle_command(cmd: String, args: PackedStringArray) -> String:
|
||||
"say", "msg", "players", "changelevel", "kick", "ban", "unban", "exec":
|
||||
return _cmd_dispatch(cmd, args)
|
||||
_:
|
||||
return "Unknown command: '" + cmd + "'. Type 'help' for available commands.\r\n"
|
||||
return "Unknown command: '" + cmd + "'. Type 'help' for available commands.\\r\\n"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Built-in commands
|
||||
@@ -90,7 +90,7 @@ func _cmd_help(_args: PackedStringArray) -> String:
|
||||
func _cmd_echo(args: PackedStringArray) -> String:
|
||||
if args.is_empty():
|
||||
return "Pong\r\n"
|
||||
return args.join(" ") + "\r\n"
|
||||
return " ".join(args) + "\\r\\n"
|
||||
|
||||
func _cmd_status(_args: PackedStringArray) -> String:
|
||||
var lines: PackedStringArray = []
|
||||
@@ -125,8 +125,8 @@ func _cmd_status(_args: PackedStringArray) -> String:
|
||||
var mem_kb: float = Performance.get_monitor(Performance.MEMORY_STATIC)
|
||||
lines.append(" Memory: " + str(snapped(mem_kb / 1024.0, 0.1)) + " MB")
|
||||
|
||||
# Node count
|
||||
lines.append(" Nodes: " + str(Performance.get_monitor(Performance.NODE_COUNT)))
|
||||
# Node count (via SceneTree)
|
||||
lines.append(" Nodes: " + str(get_tree().node_count))
|
||||
|
||||
# RCON info
|
||||
lines.append(" RCON: enabled on port ? (see rcon_server)")
|
||||
@@ -209,7 +209,9 @@ func _cmd_cvar_set(args: PackedStringArray) -> String:
|
||||
if has_node("/root/CvarRegistry"):
|
||||
var registry = get_node("/root/CvarRegistry")
|
||||
if registry and registry.has_method("set"):
|
||||
var err: int = registry.set(name, value)
|
||||
# Use Callable to avoid compile-time void return detection on dynamic object
|
||||
var fn = Callable(registry, "set")
|
||||
var err = fn.call(name, value)
|
||||
if err == OK:
|
||||
return name + " = " + value + "\r\n"
|
||||
match err:
|
||||
@@ -242,3 +244,158 @@ func _cmd_plugin(args: PackedStringArray) -> String:
|
||||
func _cmd_dispatch(cmd: String, args: PackedStringArray) -> String:
|
||||
rcon_command.emit(cmd, args)
|
||||
return "Command '" + cmd + "' dispatched to server.\r\n"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Plugin management commands — interfaces with PluginManager singleton
|
||||
# ---------------------------------------------------------------------------
|
||||
func _cmd_plugins(args: PackedStringArray) -> String:
|
||||
"""Handle 'plugins' command with subcommands."""
|
||||
if args.is_empty():
|
||||
return _cmd_plugins_help()
|
||||
|
||||
var subcmd: String = args[0].to_lower()
|
||||
|
||||
match subcmd:
|
||||
"list":
|
||||
return _cmd_plugins_list()
|
||||
"load":
|
||||
if args.size() < 2:
|
||||
return "Usage: plugins load <name>\r\n"
|
||||
return _cmd_plugins_load(args[1])
|
||||
"unload":
|
||||
if args.size() < 2:
|
||||
return "Usage: plugins unload <name>\r\n"
|
||||
return _cmd_plugins_unload(args[1])
|
||||
"reload":
|
||||
if args.size() < 2:
|
||||
return "Usage: plugins reload <name>\r\n"
|
||||
return _cmd_plugins_reload(args[1])
|
||||
"info":
|
||||
if args.size() < 2:
|
||||
return "Usage: plugins info <name>\r\n"
|
||||
return _cmd_plugins_info(args[1])
|
||||
"scan", "rescan":
|
||||
return _cmd_plugins_rescan()
|
||||
_:
|
||||
return "Unknown plugins subcommand: '" + subcmd + "'\r\n" + _cmd_plugins_help()
|
||||
|
||||
|
||||
func _cmd_plugins_help() -> String:
|
||||
return "\r\n".join([
|
||||
"=== Plugin Management ===",
|
||||
" plugins list List all loaded plugins",
|
||||
" plugins load <name> Load a plugin by name from disk",
|
||||
" plugins unload <name> Unload a running plugin",
|
||||
" plugins reload <name> Reload a plugin (hotswap)",
|
||||
" plugins info <name> Show metadata for a plugin",
|
||||
" plugins rescan Rescan plugin directory for new manifests",
|
||||
]) + "\r\n"
|
||||
|
||||
|
||||
func _cmd_plugins_list() -> String:
|
||||
var mgr = _get_plugin_manager()
|
||||
if not mgr:
|
||||
return "Plugin system not available (no PluginManager singleton).\r\n"
|
||||
|
||||
var plugins = mgr.list_plugins()
|
||||
if plugins.is_empty():
|
||||
return "No plugins loaded.\r\n"
|
||||
|
||||
var lines: PackedStringArray = []
|
||||
lines.append("=== Loaded Plugins (" + str(plugins.size()) + ") ===")
|
||||
lines.append(" %-24s %-10s %s" % ["Name", "Version", "Author"])
|
||||
lines.append(" " + "-".repeat(50))
|
||||
|
||||
for p in plugins:
|
||||
lines.append(" %-24s %-10s %s" % [p.name, p.version, p.author])
|
||||
|
||||
return "\r\n".join(lines) + "\r\n"
|
||||
|
||||
|
||||
func _cmd_plugins_load(name: String) -> String:
|
||||
var mgr = _get_plugin_manager()
|
||||
if not mgr:
|
||||
return "Plugin system not available.\r\n"
|
||||
|
||||
if not mgr.has_method("discover_plugins"):
|
||||
return "PluginManager does not support targeted loading yet — use 'plugins rescan' to reload all.\r\n"
|
||||
|
||||
if mgr.get_plugin(name):
|
||||
return "Plugin '" + name + "' is already loaded.\r\n"
|
||||
|
||||
var result = mgr.discover_plugins()
|
||||
if mgr.get_plugin(name):
|
||||
return "Plugin '" + name + "' loaded successfully.\r\n"
|
||||
else:
|
||||
return "Failed to load '" + name + "'. Check that plugin.tres exists in user://plugins/" + name.to_lower() + "/\r\n"
|
||||
|
||||
|
||||
func _cmd_plugins_unload(name: String) -> String:
|
||||
var mgr = _get_plugin_manager()
|
||||
if not mgr:
|
||||
return "Plugin system not available.\r\n"
|
||||
|
||||
if not mgr.has_method("unload_plugin"):
|
||||
return "PluginManager does not support unloading.\r\n"
|
||||
|
||||
if mgr.unload_plugin(name):
|
||||
return "Plugin '" + name + "' unloaded.\r\n"
|
||||
else:
|
||||
return "Plugin '" + name + "' not found. Use 'plugins list' to see loaded plugins.\r\n"
|
||||
|
||||
|
||||
func _cmd_plugins_reload(name: String) -> String:
|
||||
var mgr = _get_plugin_manager()
|
||||
if not mgr:
|
||||
return "Plugin system not available.\r\n"
|
||||
|
||||
if not mgr.has_method("reload_plugin"):
|
||||
return "PluginManager does not support reloading.\r\n"
|
||||
|
||||
if mgr.reload_plugin(name):
|
||||
return "Plugin '" + name + "' reloaded.\r\n"
|
||||
else:
|
||||
return "Plugin '" + name + "' not found. Use 'plugins list' to see loaded plugins.\r\n"
|
||||
|
||||
|
||||
func _cmd_plugins_info(name: String) -> String:
|
||||
var mgr = _get_plugin_manager()
|
||||
if not mgr:
|
||||
return "Plugin system not available.\r\n"
|
||||
|
||||
var plugin = mgr.get_plugin(name)
|
||||
if not plugin:
|
||||
return "Plugin '" + name + "' not found. Use 'plugins list' to see loaded plugins.\r\n"
|
||||
|
||||
var lines: PackedStringArray = []
|
||||
lines.append("=== Plugin: " + plugin.plugin_name + " ===")
|
||||
lines.append(" Name: " + plugin.plugin_name)
|
||||
lines.append(" Version: " + plugin.plugin_version)
|
||||
lines.append(" Author: " + plugin.plugin_author)
|
||||
lines.append(" Description: " + plugin.plugin_description)
|
||||
lines.append(" Manifest: " + plugin.plugin_manifest_path)
|
||||
return "\r\n".join(lines) + "\r\n"
|
||||
|
||||
|
||||
func _cmd_plugins_rescan() -> String:
|
||||
var mgr = _get_plugin_manager()
|
||||
if not mgr:
|
||||
return "Plugin system not available.\r\n"
|
||||
|
||||
var result = mgr.discover_plugins()
|
||||
return "Rescan complete — loaded: " + str(result.get("loaded", 0)) + \
|
||||
", failed: " + str(result.get("failed", 0)) + \
|
||||
", skipped: " + str(result.get("skipped", 0)) + "\r\n"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helper to locate PluginManager singleton
|
||||
# ---------------------------------------------------------------------------
|
||||
func _get_plugin_manager():
|
||||
"""Get the PluginManager singleton, or null if not available."""
|
||||
if Engine.has_singleton("PluginManager"):
|
||||
return Engine.get_singleton("PluginManager")
|
||||
if has_node("/root/PluginManager"):
|
||||
return get_node("/root/PluginManager")
|
||||
return null
|
||||
|
||||
Reference in New Issue
Block a user