Files
godot-demo-projects/2d/physics_tests/tests/performance/test_perf_contacts.gd
2025-10-11 05:03:59 -07:00

207 lines
5.4 KiB
GDScript

extends Test
const OPTION_TYPE_ALL = "Shape type/All"
const OPTION_TYPE_RECTANGLE = "Shape type/Rectangle"
const OPTION_TYPE_SPHERE = "Shape type/Sphere"
const OPTION_TYPE_CAPSULE = "Shape type/Capsule"
const OPTION_TYPE_CONVEX_POLYGON = "Shape type/Convex Polygon"
const OPTION_TYPE_CONCAVE_POLYGON = "Shape type/Concave Polygon"
@export var spawns: Array[NodePath] = []
@export var spawn_count := 100
@export var spawn_randomize := Vector2.ZERO
@onready var options: OptionMenu = $Options
var _object_templates: Array[Node2D] = []
var _log_physics: bool = false
var _log_physics_time := 0
var _log_physics_time_start := 0
func _ready() -> void:
await start_timer(0.5).timeout
if is_timer_canceled():
return
var dynamic_shapes: Node2D = $DynamicShapes
while dynamic_shapes.get_child_count():
var type_node: RigidBody2D = dynamic_shapes.get_child(0)
type_node.position = Vector2.ZERO
_object_templates.push_back(type_node)
dynamic_shapes.remove_child(type_node)
options.add_menu_item(OPTION_TYPE_ALL)
options.add_menu_item(OPTION_TYPE_RECTANGLE)
options.add_menu_item(OPTION_TYPE_SPHERE)
options.add_menu_item(OPTION_TYPE_CAPSULE)
options.add_menu_item(OPTION_TYPE_CONVEX_POLYGON)
options.add_menu_item(OPTION_TYPE_CONCAVE_POLYGON)
options.option_selected.connect(_on_option_selected)
await _start_all_types()
func _physics_process(delta: float) -> void:
super._physics_process(delta)
if _log_physics:
var time := Time.get_ticks_usec()
var time_delta := time - _log_physics_time
var time_total := time - _log_physics_time_start
_log_physics_time = time
Log.print_log(" Physics Tick: %.3f ms (total = %.3f ms)" % [0.001 * time_delta, 0.001 * time_total])
func _log_physics_start() -> void:
_log_physics = true
_log_physics_time_start = Time.get_ticks_usec()
_log_physics_time = _log_physics_time_start
func _log_physics_stop() -> void:
_log_physics = false
func _exit_tree() -> void:
for object_template in _object_templates:
object_template.free()
func _on_option_selected(option: String) -> void:
cancel_timer()
_despawn_objects()
match option:
OPTION_TYPE_ALL:
await _start_all_types()
OPTION_TYPE_RECTANGLE:
await _start_type(_find_type_index("Rectangle"))
OPTION_TYPE_SPHERE:
await _start_type(_find_type_index("Sphere"))
OPTION_TYPE_CAPSULE:
await _start_type(_find_type_index("Capsule"))
OPTION_TYPE_CONVEX_POLYGON:
await _start_type(_find_type_index("ConvexPolygon"))
OPTION_TYPE_CONCAVE_POLYGON:
await _start_type(_find_type_index("ConcavePolygon"))
func _find_type_index(type_name: String) -> int:
for type_index in range(_object_templates.size()):
var type_node := _object_templates[type_index]
if String(type_node.name).find(type_name) > -1:
return type_index
Log.print_error("Invalid shape type: " + type_name)
return -1
func _start_type(type_index: int) -> void:
if type_index < 0:
return
if type_index >= _object_templates.size():
return
await start_timer(1.0).timeout
if is_timer_canceled():
return
_log_physics_start()
_spawn_objects(type_index)
await wait_for_physics_ticks(5).wait_done
_log_physics_stop()
await start_timer(1.0).timeout
if is_timer_canceled():
return
_log_physics_start()
_activate_objects()
await wait_for_physics_ticks(5).wait_done
_log_physics_stop()
await start_timer(5.0).timeout
if is_timer_canceled():
return
_log_physics_start()
_despawn_objects()
await wait_for_physics_ticks(5).wait_done
_log_physics_stop()
await start_timer(1.0).timeout
func _start_all_types() -> void:
Log.print_log("* Start all types.")
for type_index in range(_object_templates.size()):
await _start_type(type_index)
if is_timer_canceled():
return
Log.print_log("* Done all types.")
func _spawn_objects(type_index: int) -> void:
var template_node: RigidBody2D = _object_templates[type_index]
Log.print_log("* Spawning: " + String(template_node.name))
for spawn in spawns:
var spawn_parent := get_node(spawn)
for _node_index in range(spawn_count):
# Create a new object and shape every time to avoid the overhead of connecting many bodies to the same shape.
var collision: Node2D = template_node.get_child(0).duplicate()
if collision is CollisionShape2D:
collision.shape = collision.shape.duplicate()
var body: Node2D = template_node.duplicate()
body.transform = Transform2D.IDENTITY
if spawn_randomize != Vector2.ZERO:
body.position.x = randf() * spawn_randomize.x
body.position.y = randf() * spawn_randomize.y
var prev_collision: Node2D = body.get_child(0)
body.remove_child(prev_collision)
prev_collision.queue_free()
body.add_child(collision)
body.set_sleeping(true)
spawn_parent.add_child(body)
func _activate_objects() -> void:
Log.print_log("* Activating")
for spawn in spawns:
var spawn_parent := get_node(spawn)
for node_index in spawn_parent.get_child_count():
var node := spawn_parent.get_child(node_index) as RigidBody2D
node.set_sleeping(false)
func _despawn_objects() -> void:
Log.print_log("* Despawning")
for spawn in spawns:
var spawn_parent: Node2D = get_node(spawn)
var object_count := spawn_parent.get_child_count()
if object_count == 0:
continue
# Remove objects in reversed order to avoid the overhead of changing child index in parent.
for object_index in range(object_count):
var node: Node2D = spawn_parent.get_child(object_count - object_index - 1)
spawn_parent.remove_child(node)
node.queue_free()