OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / _probe_move_cost.gd

114 lines · gd ref:
extends SceneTree
## Probe (user question 2026-10-03): while MOVING brushes, show the real brushes. Which
## costs less latency per step?
##   A) live: translate_brush + DCLevel.rebuild() on every step;
##   B) proxy: hide the brush + rebuild ONCE (lift), build a mesh from its faces, move
##      that mesh by transform per step (no rebuild), unhide + translate + rebuild ONCE
##      on drop.
## Measured headless on map_flat with N textured 1 m boxes (CPU side: mesh + collision
## build; GPU upload is not included). Run:
##   HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/_probe_move_cost.gd

const SIZES := [50, 200, 800]
const STEPS := 20

var _frame := 0
var _map: Node


func _process(_d: float) -> bool:
	_frame += 1
	if _frame == 1:
		_map = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
		root.add_child(_map)
		return false
	if _frame < 30:
		return false
	_run()
	quit(0)
	return true


func _box(lo: Vector3, hi: Vector3) -> PackedFloat32Array:
	return PackedFloat32Array([1, 0, 0, hi.x, -1, 0, 0, -lo.x, 0, 1, 0, hi.y, 0, -1, 0, -lo.y, 0, 0, 1, hi.z, 0, 0, -1, -lo.z])


static func _ms(t0: int) -> float:
	return (Time.get_ticks_usec() - t0) / 1000.0


static func _med(a: Array) -> float:
	var s := a.duplicate()
	s.sort()
	return s[s.size() / 2]


## A mesh of one brush's faces (fan-triangulated windings). Material-less: this
## measures the cost of making the proxy, not how it looks.
func _proxy_mesh(level: Node, b: int) -> ArrayMesh:
	var st := SurfaceTool.new()
	st.begin(Mesh.PRIMITIVE_TRIANGLES)
	var planes: PackedFloat32Array = level.get_brush(b)["planes"]
	for f in planes.size() / 4:
		var w: PackedVector3Array = level.get_face_winding(b, f)
		if w.size() < 3:
			continue
		var n := Vector3(planes[f * 4], planes[f * 4 + 1], planes[f * 4 + 2])
		for i in range(1, w.size() - 1):
			for p: Vector3 in [w[0], w[i], w[i + 1]]:
				st.set_normal(n)
				st.add_vertex(p)
	return st.commit()


func _run() -> void:
	var level: Node = _map.get_node("DCLevel")
	var ft: Array = []
	for i in 6:
		ft.append({"tex": "kenney-prototype/dark_01", "mode": 1})
	var made := 0
	for n: int in SIZES:
		while made < n:
			var x := float(made % 30) * 1.5
			var z := float(made / 30) * 1.5
			level.add_brush(_box(Vector3(x, 0, z), Vector3(x + 1, 1, z + 1)), 1, PackedInt32Array(), 0,
				false, "probe-%d" % made, false, "", ft)
			made += 1
		var t0 := Time.get_ticks_usec()
		level.rebuild()
		var base := _ms(t0)
		var ids: Array = level.get_live_brush_ids()
		var b: int = ids[ids.size() / 2]

		# A) live rebuild per step
		var a_steps: Array = []
		for s in STEPS:
			t0 = Time.get_ticks_usec()
			level.translate_brush(b, Vector3(0.028, 0, 0), true)
			level.rebuild()
			a_steps.append(_ms(t0))

		# B) proxy
		t0 = Time.get_ticks_usec()
		level.set_brush_hidden(b, true)
		level.rebuild()
		var lift := _ms(t0)
		t0 = Time.get_ticks_usec()
		var mi := MeshInstance3D.new()
		mi.mesh = _proxy_mesh(level, b)
		root.add_child(mi)
		var build := _ms(t0)
		var b_steps: Array = []
		for s in STEPS:
			t0 = Time.get_ticks_usec()
			mi.position += Vector3(0.028, 0, 0)
			b_steps.append(_ms(t0))
		t0 = Time.get_ticks_usec()
		level.set_brush_hidden(b, false)
		level.translate_brush(b, Vector3(0.028 * STEPS, 0, 0), true)
		level.rebuild()
		var drop := _ms(t0)
		mi.queue_free()
		print("PROBE N=%4d tris=%6d | full rebuild %7.2f ms | A per step median %7.2f max %7.2f | B lift %7.2f proxy %6.3f per step %6.4f drop %7.2f" % [
			n, level.get_triangle_count(), base, _med(a_steps), a_steps.max(), lift, build, _med(b_steps), drop])