OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / _probe_cable_fold_time.gd

136 lines · gd ref:
extends Node
## PROBE (not a gate) — fold cost of the L3 cable extras (#120, docs/node-tree-and-stacks.md
## §14.8 step 7, "measured, not asserted"), the same method as _probe_mod_fold_time: a FULL
## refold (the brush half forced), median of 3, and the live brush count, after each stage:
##   base     the map, no cables
##   cable    + one straight-ish 10 m cable, the defaults (round 8, max_len 1 m)
##   strands  that cable with 7 strands
##   bundle   + a 20-cable bundle between two boxes 10 m apart (slack 1 m: they hang and bend)
##   bundle7  the bundle's cables with 7 strands each
## A cable's curve edit re-folds IN FULL (§11.2: a followed curve), so each line is also what
## one curve edit costs in that room.
##
## RUN: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . res://scenes/test/_probe_cable_fold_time.tscn

const BundleCables := preload("res://scenes/maps/curves/bundle_cables.gd")
const ParamShapes := preload("res://scenes/maps/shapes/param/param_shapes.gd")
const ShapeFold := preload("res://scenes/maps/shapes/param/shape_fold.gd")

var persist: Node
var level: Node
var map: Node3D


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"
	get_tree().root.get_node("/root/GameMode").set_mode("build")
	map = (load("res://scenes/maps/map_dev_test.tscn") as PackedScene).instantiate()
	add_child(map)
	for i in 6:
		await get_tree().process_frame
	persist = map.get("_persist")
	level = map.get("level")
	var rows: Array = []
	rows.append(["base", _time(), _n()])
	# ---- one 10 m cable ----
	var c := map.add_cable([CurveMath.point(Vector3(300, 2, 300), Vector3.ZERO), CurveMath.point(Vector3(305, 3, 302), Vector3.ZERO),
		CurveMath.point(Vector3(310, 2, 300), Vector3.ZERO)], false, {}) as String
	rows.append(["cable", _time(), _n()])
	_set_params(c, {"strands": 7})
	rows.append(["strands 7", _time(), _n()])
	# ---- a 20-cable bundle: the facing sides of two boxes 10 m apart ----
	var A := {"pts": PackedVector3Array([Vector3(400, 0, 300), Vector3(400, 0, 302), Vector3(400, 2, 302), Vector3(400, 2, 300)]), "n": Vector3.RIGHT}
	var B := {"pts": PackedVector3Array([Vector3(410, 0, 300), Vector3(410, 2, 300), Vector3(410, 2, 302), Vector3(410, 0, 302)]), "n": Vector3.LEFT}
	var dots: Array = map.cable_bundle([A, B], {"count": 20, "random": true, "seed": 1, "tension": 1.0,
		"jitter": 0.3, "slack": 1.0}, {"radius": 0.03})
	rows.append(["bundle 20", _time(), _n()])
	_split(dots)
	persist.begin_gesture("strands")
	for d: String in dots:
		persist.record("shape.set", {"target": d, "params": {"strands": 7}})
	persist.end_gesture()
	rows.append(["bundle 20 x 7 strands", _time(), _n()])
	var prev := 0
	for r: Array in rows:
		print("PROBE %-24s full refold %8.1f ms   %6d live brushes  (+%d)" % [r[0], r[1], r[2], int(r[2]) - prev])
		prev = int(r[2])
	print("PROBE cables in the bundle: %d" % dots.size())
	get_tree().quit(0)


func _set_params(dot: String, p: Dictionary) -> void:
	persist.begin_gesture("probe")
	persist.record("shape.set", {"target": dot, "params": p})
	persist.end_gesture()


## WHERE THE BUNDLE'S FOLD TIME GOES, the generator alone (no level), median of 3:
## world_pieces for the 20 cables; its fine CurveMath.sample alone; and DCLevel.hull_of alone
## on the same rings (the hulls world_pieces makes, re-hulled from their corners).
func _split(dots: Array) -> void:
	var t: Object = ParamShapes.get_type("cable")
	var curves: Dictionary = persist.curve_states()
	var jobs: Array = []
	for d: String in dots:
		var p: Dictionary = t.resolve(persist.shape_state(d)["params"])
		jobs.append([curves[String(p["curve"])], p])
	var wp: Array = []
	var smp: Array = []
	var hl: Array = []
	var pieces: Array = []
	for rep in 3:
		var t0 := Time.get_ticks_usec()
		pieces = []
		for j: Array in jobs:
			pieces.append_array(t.world_pieces(j[0], j[1]))
		wp.append(float(Time.get_ticks_usec() - t0) / 1000.0)
		t0 = Time.get_ticks_usec()
		for j2: Array in jobs:
			var cv: Dictionary = j2[0]
			var L := CurveMath.length(cv["points"], bool(cv["closed"]))
			CurveMath.sample(cv["points"], bool(cv["closed"]), clampi(ceili(L / 0.02) + 1, 16, 4096))
		smp.append(float(Time.get_ticks_usec() - t0) / 1000.0)
		var pts_all: Array = []
		for pc: Dictionary in pieces:
			pts_all.append(FaceKit.corners(pc["planes"]))
		t0 = Time.get_ticks_usec()
		for cs: PackedVector3Array in pts_all:
			DCLevel.hull_of(cs)
		hl.append(float(Time.get_ticks_usec() - t0) / 1000.0)
	wp.sort()
	smp.sort()
	hl.sort()
	print("PROBE split (bundle 20, %d pieces): world_pieces %.1f ms | its fine sampling %.1f ms | hull_of alone %.1f ms"
		% [pieces.size(), wp[1], smp[1], hl[1]])
	# ShapeFold.expand on the room's shape records: the generator + resolve_states + the
	# brush.create records. What a full refold spends beyond this is the level's side.
	var shape_recs: Array = (persist.records(false) as Array).filter(func(r: Dictionary) -> bool: return ShapeFold.handles(String(r["kind"])))
	var ex: Array = []
	for rep2 in 3:
		var t1 := Time.get_ticks_usec()
		ShapeFold.expand(shape_recs, {}, curves)
		ex.append(float(Time.get_ticks_usec() - t1) / 1000.0)
	ex.sort()
	print("PROBE split: ShapeFold.expand (whole room) %.1f ms" % ex[1])


func _n() -> int:
	return (level.get_live_brush_ids() as PackedInt32Array).size()


## A full refold, median of 3 (as _probe_mod_fold_time).
func _time() -> float:
	var ts: Array = []
	for i in 3:
		persist.set("_brush_ver_folded", -1)     # force the brush half
		persist.set("_ix_tail_ok", false)
		var t0 := Time.get_ticks_usec()
		persist.refold()
		ts.append(float(Time.get_ticks_usec() - t0) / 1000.0)
	ts.sort()
	return ts[1]