OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / _probe_history_ops.gd

125 lines · gd ref:
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)