extends Node
## PROBE (not a gate): what does each editor operation cost with a LARGE history?
## Builds ~2500 level records (one 2000-record gesture like the accidental Subtract, plus
## 59 small ones) on map_test, then times the user-facing operations end to end — the
## journal write, the refold, the History panel's refresh, the Outliner's refresh.
##
## Run: "$HYPER_TPS_GODOT" --headless --path . res://scenes/test/_probe_history_ops.tscn
## Env PROBE_BIG=<n> changes the big gesture's size (default 2000).
func _ready() -> void:
var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
gs.route("world:level:*", 1, "peer")
var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
if keet != null:
keet.current_member_id = "probe-author"
get_tree().root.get_node("/root/GameMode").set_mode("build")
var map: Node3D = (load("res://scenes/maps/map_test.tscn") as PackedScene).instantiate()
add_child(map)
for i in 6:
await get_tree().process_frame
var persist: Node = map.get("_persist")
var big := int(OS.get_environment("PROBE_BIG")) if OS.get_environment("PROBE_BIG") != "" else 2000
var t0 := Time.get_ticks_usec()
for g in 60:
persist.begin_gesture("G%d" % g)
for i in (big if g == 0 else 8):
var x := 60.0 + float(i % 40) * 3.0
var z := float(i / 40) * 3.0 + float(g) * 200.0
persist.record("brush.create", {"planes": [1, 0, 0, x + 2, -1, 0, 0, -x, 0, 1, 0, 2, 0, -1, 0, 0, 0, 0, 1, z + 2, 0, 0, -1, -z],
"proj": [], "material_id": 0, "level_id": 0, "sky": false})
persist.end_gesture()
var write_ms := (Time.get_ticks_usec() - t0) / 1000.0
persist.refold()
var n: int = persist.records().size()
print("PROBE records %d, live brushes %d" % [n, (map.level.get_live_brush_ids() as PackedInt32Array).size()])
print("PROBE %-34s %9.1f ms (%.3f ms/record)" % ["write %d records" % (big + 59 * 8), write_ms, write_ms / float(big + 59 * 8)])
var st := {"od": ""} # lambdas capture locals BY VALUE
var ops := {
"object create (+fold)": func() -> void: st["od"] = map.create_object("marker", Transform3D(Basis.IDENTITY, Vector3(1, 1, 1))),
"object move (+fold)": func() -> void: map.move_object(st["od"], Transform3D(Basis.IDENTITY, Vector3(2, 1, 1))),
"object prop (+fold)": func() -> void: map.set_object_props(st["od"], {"tint": [0.1, 0.2, 0.3]}),
"object delete (+fold)": func() -> void: map.delete_objects([st["od"]]),
"undo (+fold)": func() -> void:
persist.undo_last()
persist.refold(),
"redo (+fold)": func() -> void:
persist.redo_last()
persist.refold(),
"brush delete x1 (+fold)": func() -> void: map.delete_brushes([map.level.brush_by_key("i:1:3")]),
"undo brush delete (+fold)": func() -> void:
persist.undo_last()
persist.refold(),
"records()": func() -> void: persist.records(),
"gestures()": func() -> void: persist.gestures(),
"undo_depth()+redo_depth()": func() -> void:
persist.undo_depth()
persist.redo_depth(),
"History refresh": func() -> void: map.get("_history").refresh(),
"Outliner refresh": func() -> void: map.get("_outliner").refresh(),
}
for label: String in ops:
var t := Time.get_ticks_usec()
(ops[label] as Callable).call()
if bool(persist.get("_dirty")):
persist.refold() # the next frame's fold, inside the timing
print("PROBE %-34s %9.1f ms" % [label, (Time.get_ticks_usec() - t) / 1000.0])
if label == "object create (+fold)":
print("PROBE (created %s)" % st["od"])
# BREAKDOWN of one object move: the write, the bare fold (listeners off), and each
# listener's own refresh after the change.
var conns: Array = persist.folded.get_connections()
for c: Dictionary in conns:
persist.folded.disconnect(c["callable"])
var t1 := Time.get_ticks_usec()
map.move_object(st["od"], Transform3D(Basis.IDENTITY, Vector3(3, 1, 1)))
var t2 := Time.get_ticks_usec()
persist.refold()
var t3 := Time.get_ticks_usec()
persist.gestures()
var t4 := Time.get_ticks_usec()
map.get("_history").refresh()
var t5 := Time.get_ticks_usec()
map.get("_outliner").refresh()
var t6 := Time.get_ticks_usec()
print("PROBE breakdown: write %.1f fold %.1f gestures(after change) %.1f History %.1f Outliner %.1f ms"
% [(t2 - t1) / 1000.0, (t3 - t2) / 1000.0, (t4 - t3) / 1000.0, (t5 - t4) / 1000.0, (t6 - t5) / 1000.0])
# BRUSH EDIT breakdown: the journal replay vs each rebuild phase (DCLevel.get_stats).
var lv: Node = map.level
var recs: Array = persist.records(false)
var ta := Time.get_ticks_usec()
lv.apply_journal(recs)
var tb := Time.get_ticks_usec()
lv.rebuild()
var tc := Time.get_ticks_usec()
var gst: Dictionary = lv.get_stats()
var phases := {}
for k: String in gst:
if k.begins_with("ms_") or k.ends_with("_ms"):
phases[k] = snappedf(float(gst[k]), 0.1)
print("PROBE brush breakdown: records(false) %d apply_journal %.1f ms rebuild %.1f ms phases %s"
% [recs.size(), (tb - ta) / 1000.0, (tc - tb) / 1000.0, str(phases)])
# BRUSH DELETE, listeners off, then each folded listener timed on its own.
var names := PackedStringArray()
for c: Dictionary in conns:
names.append(str((c["callable"] as Callable).get_method()) + "@" + str((c["callable"] as Callable).get_object()))
print("PROBE folded listeners: %s" % ", ".join(names))
var tq := Time.get_ticks_usec()
map.delete_brushes([map.level.brush_by_key("i:1:4")])
var tr := Time.get_ticks_usec()
print("PROBE brush delete, listeners off: %.1f ms (fold %s)" % [(tr - tq) / 1000.0, str(persist.last_fold().get("brush_fold", ""))])
for c: Dictionary in conns:
var tl := Time.get_ticks_usec()
(c["callable"] as Callable).call(persist.last_fold())
print("PROBE listener %s: %.1f ms" % [str((c["callable"] as Callable).get_method()), (Time.get_ticks_usec() - tl) / 1000.0])
var tw := Time.get_ticks_usec()
map._refresh_brush_view()
var tx := Time.get_ticks_usec()
map.get("_outliner").refresh()
var ty := Time.get_ticks_usec()
print("PROBE wireframe refresh %.1f ms, Outliner refresh (brush change) %.1f ms" % [(tx - tw) / 1000.0, (ty - tx) / 1000.0])
for c: Dictionary in conns:
persist.folded.connect(c["callable"])
get_tree().quit(0)