-
Notifications
You must be signed in to change notification settings - Fork 0
Memory Ownership
Tensor Planner crosses native and managed boundaries, so lifetime rules matter.
The C API uses explicit ownership.
Destroy handles you create:
tp_solver_destroy(solver);
tp_state_destroy(state);
tp_domain_destroy(domain);Dispose output structs filled by API calls:
tp_schema_tensors_dispose(&schema);
tp_problem_tensors_dispose(&problem);
tp_action_graph_dispose(&graph);
tp_candidate_action_list_dispose(&candidates);
tp_solve_result_dispose(&result);Rule of thumb:
- If the API returns a handle, call its matching destroy function.
- If the API fills an output struct containing pointers, call its matching dispose function.
Scorer callbacks receive borrowed pointers:
const TP_Schema_Tensors *schema
const TP_Problem_Tensors *problemDo not store these pointers after the callback returns.
TP_Solve_Result owns plan_actions after a successful solve call. Release it
with:
tp_solve_result_dispose(&result);Do not read result.plan_actions after disposal.
The C++ wrapper manages native domain and solver handles through tp::Planner.
However, state objects are borrowed from your application:
auto state = planner.state()
.object(player)
.object(home)
.fact(at(player, home));Plan steps return pointers to those same objects:
Character *who = step.arg<Character>("who");Therefore:
- keep registered objects alive while reading the result,
- keep the state/result alive while using plan-step object pointers,
- do not register temporary objects.
Avoid:
auto state = planner.state().object(Character{"temporary"}); // badPrefer:
Character player{"player"};
auto state = planner.state().object(player); // goodPlanner implements IDisposable:
using (Planner planner = new Planner()) {
// use planner
}StateBuilder stores managed object references for objects added with
.Object(...). Plan steps return those same references:
Character who = step.Arg<Character>("who");Keep these alive while reading results:
-
Planner, -
StateBuilder, -
SolveResult, - application objects registered in the state.
For SolveAsync, keep Planner, StateBuilder, and registered application
objects alive until the returned task completes. Do not dispose the planner while
an async solve is running. The wrapper allows only one active solve operation per
planner instance.
Unity loads native plugins from the package's Runtime/Plugins folder. Do not
delete or replace the plugin while planner instances exist.
Native plugin paths:
Runtime/Plugins/x86_64/libtensor_planner.so
Runtime/Plugins/x86_64/tensor_planner.dll
The Jai wrapper exposes explicit destroy helpers:
planner := planner_create(Crafting);
defer planner_destroy(*planner);
result := solve(*planner, *state);
defer solve_result_destroy(*result);Objects registered with object(*state, *value, name) are borrowed pointers to
your Jai values. Keep those values alive while using plan-step pointers returned
by step_arg.
- C: always destroy handles and dispose output structs.
- C++: do not register temporaries; keep objects alive.
- C#: use
using; keep state/result references alive while reading steps. - Unity: make sure the native plugin exists for the target platform.
- Jai: use
defer planner_destroyanddefer solve_result_destroy.