Skip to content

ovphysx_step_sync slows down with ovphysx_update_from_ovstage cycles #499

Description

@EliasStoffels

Library and Version

ovphysx 0.5.9, ovstage 0.1.0.346039

Operating System

Linux x86_64

Steps to Trigger Behavior

  1. Create an ovphysx instance and an ovstage instance; attach a minimal seed scene.
  2. Loop: remove the previous cycle's /Spawned_N USD reference (ovstage_population_remove_usd_reference, skip on the first iteration)-> author and add a freshly-named /Spawned_N+1 reference (ovstage_population_add_usd_reference_from_string)-> ovstage_population_apply_usd_changes-> ovstage_advance_write_floor -> ovphysx_update_from_ovstage (drains everything sealed so far) ->5x ovphysx_step_sync.
  3. ovphysx_step_sync continuesly slows down and hangs around cycle 27.

Code Snippet to Reproduce Behavior

#include <chrono>
#include <cstdio>
#include <string>
#include <ovphysx/ovphysx.h>
#include <ovstage/ovstage.h>
#include <ovstage/ovstage_population.h>

using Clock = std::chrono::steady_clock;
static double us_since(Clock::time_point t0) {
    return std::chrono::duration<double, std::micro>(Clock::now() - t0).count();
}

static bool wait_pop(ovstage_instance_t* s, ovstage_population_enqueue_result_t r) {
    if (r.status != OVSTAGE_OK) return false;
    ovstage_population_op_wait_result_t w{};
    return ovstage_population_wait_op(s, r.op_index, OVSTAGE_TIMEOUT_INFINITE, &w) == OVSTAGE_OK;
}

static bool floor(ovstage_instance_t* s, ovstage_ordinal_t o) {
    ovstage_write_floor_desc_t d{};
    d.ordinal = o;
    d.scope = OVSTAGE_SCOPE_ALL;
    ovstage_enqueue_result_t r = ovstage_advance_write_floor(s, &d);
    if (r.status != OVSTAGE_OK) return false;
    ovstage_op_wait_result_t w{};
    return ovstage_wait_op(s, r.op_index, OVSTAGE_TIMEOUT_INFINITE, &w) == OVSTAGE_OK;
}

static std::string cube(const std::string& n) {
    return "#usda 1.0\n(\n    defaultPrim = \"" + n + "\"\n)\n\n"
           "def Cube \"" + n + "\" (\n"
           "    prepend apiSchemas = [\"PhysicsRigidBodyAPI\", \"PhysicsCollisionAPI\", \"PhysicsMassAPI\"]\n)\n{\n"
           "    double size = 1\n"
           "    double3 xformOp:translate = (0, 3, 0)\n"
           "    uniform token[] xformOpOrder = [\"xformOp:translate\"]\n"
           "    float physics:mass = 1\n"
           "}\n";
}

static bool add_ref(ovstage_instance_t* s, const std::string& usda, const std::string& tgt,
                     ovstage_population_usd_reference_handle_t* h) {
    ovx_string_t u{usda.c_str(), usda.size()};
    ovx_string_t t{tgt.c_str(), tgt.size()};
    return wait_pop(s, ovstage_population_add_usd_reference_from_string(s, u, t, h));
}

int main() {
    ovphysx_initialize();

    ovphysx_handle_t handle = 0;
    ovphysx_create_args args = OVPHYSX_CREATE_ARGS_DEFAULT;
    if (ovphysx_create_instance(&args, &handle).status != OVPHYSX_API_SUCCESS) return 1;

    ovstage_instance_desc_t d{};
    d.name = "repro";
    ovstage_instance_t* stage = nullptr;
    if (ovstage_create_instance(&d, &stage) != OVSTAGE_OK) return 1;

    ovstage_ordinal_t ord = 1;
    std::string meta = "#usda 1.0\n(\n    upAxis = \"Y\"\n    metersPerUnit = 1\n)\n";
    ovx_string_t metaStr{meta.c_str(), meta.size()};
    if (!wait_pop(stage, ovstage_population_open_usd_from_string(stage, metaStr, ord, 0.0, OVSTAGE_POPULATION_DOMAIN_PHYSICS))) return 1;
    if (!floor(stage, ord)) return 1;
    ++ord;

    std::string ground =
        "#usda 1.0\n(\n    defaultPrim = \"Ground\"\n)\n\n"
        "def Plane \"Ground\" (\n    prepend apiSchemas = [\"PhysicsCollisionAPI\"]\n)\n{\n"
        "    uniform token axis = \"Y\"\n    double width = 100\n    double length = 100\n}\n";
    ovstage_population_usd_reference_handle_t groundHandle{}, fallingHandle{}, spawnedHandle{};
    if (!add_ref(stage, ground, "/Ground", &groundHandle)) return 1;
    if (!add_ref(stage, cube("FallingCube"), "/FallingCube", &fallingHandle)) return 1;
    if (!wait_pop(stage, ovstage_population_apply_usd_changes(stage, ord))) return 1;
    if (!floor(stage, ord)) return 1;
    if (ovphysx_attach_ovstage(handle, stage, ord).status != OVPHYSX_API_SUCCESS) return 1;
    ++ord;

    for (int cycle = 1; cycle <= 40; ++cycle) {
        if (cycle > 1 && !wait_pop(stage, ovstage_population_remove_usd_reference(stage, spawnedHandle))) return 1;

        std::string name = "Spawned_" + std::to_string(cycle);
        if (!add_ref(stage, cube(name), "/" + name, &spawnedHandle)) return 1;
        if (!wait_pop(stage, ovstage_population_apply_usd_changes(stage, ord))) return 1;
        if (!floor(stage, ord)) return 1;

        ovstage_ordinal_range_t range{0, ord, false};
        if (ovphysx_update_from_ovstage(handle, range).status != OVPHYSX_API_SUCCESS) return 1;
        ++ord;

        double total_us = 0;
        for (int i = 0; i < 5; ++i) {
            Clock::time_point t0 = Clock::now();
            if (ovphysx_step_sync(handle, 1.0f / 60.0f).status != OVPHYSX_API_SUCCESS) return 1;
            total_us += us_since(t0);
        }

        fprintf(stderr, "cycle %2d avg_step_sync_us=%.0f\n", cycle, total_us / 5.0);
        fflush(stderr);
    }

    return 0;
}

Expected Behavior

Adding/removing singular prims to not have a significant effect on ovphysx_step_sync per call cost.

Actual Behavior

Ovphysx_step_sync's own per-call cost climbs from ~5.3ms (cycle 1) to ~9.0ms (cycle 26) before the cycle-27 hang, suggesting it's an accumulation error.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions