Closed Capital-Asterisk closed 1 month ago
Here's a bit of a mockup of a new interface I'm planning:
src/testapp/identifiers.h
:
#define TESTAPP_DATA_PHYS_SHAPES 1, \
idPhysShapes
struct PlPhysShapes
{
PipelineDef<EStgIntr> spawnRequest {"spawnRequest - Spawned shapes"};
PipelineDef<EStgIntr> spawnedEnts {"spawnedEnts"};
PipelineDef<EStgRevd> ownedEnts {"ownedEnts"};
};
src/testapp/sessions/physics.cpp
:
Session setup_phys_shapes(
TopTaskBuilder& rBuilder,
ArrayView<entt::any> const topData,
Session const& scene,
Session const& commonScene,
Session const& physics,
MaterialId const materialId)
{
OSP_DECLARE_GET_DATA_IDS(commonScene, TESTAPP_DATA_COMMON_SCENE);
OSP_DECLARE_GET_DATA_IDS(physics, TESTAPP_DATA_PHYSICS);
auto const tgScn = scene .get_pipelines<PlScene>();
auto const tgCS = commonScene .get_pipelines<PlCommonScene>();
auto const tgPhy = physics .get_pipelines<PlPhysics>();
Session out;
OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_PHYS_SHAPES);
auto const tgShSp = out.create_pipelines<PlPhysShapes>(rBuilder);
rBuilder.pipeline(tgShSp.spawnRequest) .parent(tgScn.update);
rBuilder.pipeline(tgShSp.spawnedEnts) .parent(tgScn.update);
rBuilder.pipeline(tgShSp.ownedEnts) .parent(tgScn.update);
top_emplace< ACtxPhysShapes > (topData, idPhysShapes, ACtxPhysShapes{ .m_materialId = materialId });
rBuilder.task()
.name ("Schedule Shape spawn")
.schedules ({tgShSp.spawnRequest(Schedule_)})
.sync_with ({tgScn.update(Run)})
.push_to (out.m_tasks)
.args ({ idPhysShapes })
.func([] (ACtxPhysShapes& rPhysShapes) noexcept -> TaskActions
{
return rPhysShapes.m_spawnRequest.empty() ? TaskAction::Cancel : TaskActions{};
});
// ...
src/testapp/scenarios.cpp
:
#define SCENE_SESSIONS scene, commonScene, physics, physShapes, droppers, bounds, newton, nwtGravSet, nwtGrav, physShapesNwt
#define RENDERER_SESSIONS sceneRenderer, magnumScene, cameraCtrl, cameraFree, shVisual, shFlat, shPhong, camThrow, shapeDraw, cursor
using namespace testapp::scenes;
auto const defaultPkg = rTestApp.m_defaultPkg;
auto const application = rTestApp.m_application;
auto & rTopData = rTestApp.m_topData;
TopTaskBuilder builder{rTestApp.m_tasks, rTestApp.m_scene.m_edges, rTestApp.m_taskData};
auto & [SCENE_SESSIONS] = resize_then_unpack<10>(rTestApp.m_scene.m_sessions);
// Compose together lots of Sessions
scene = setup_scene (builder, rTopData, application);
commonScene = setup_common_scene (builder, rTopData, scene, application, defaultPkg);
physics = setup_physics (builder, rTopData, scene, commonScene);
physShapes = setup_phys_shapes (builder, rTopData, scene, commonScene, physics, sc_matPhong);
// ...
src/testapp/identifiers.h
:
struct FIPhysShapes
{
struct DataIds
{
TopDataId physShapes;
};
struct Pipelines
{
PipelineDef<EStgIntr> spawnRequest {"spawnRequest - Spawned shapes"};
PipelineDef<EStgIntr> spawnedEnts {"spawnedEnts"};
PipelineDef<EStgRevd> ownedEnts {"ownedEnts"};
};
};
src/testapp/sessions/physics.cpp
:
void ft_phys_shapes( FeatureBuilder fb )
{
fb.name("Physics Shapes");
auto [diScn, plScn] = fb.use_interface<FIScene>();
auto [diCS, plCS] = fb.use_interface<FICommonScene>();
auto [diPhy, plPhy] = fb.use_interface<FIGenericPhysics>();
auto [dPhSh, lPhSh] = fb.implement_interface<FIPhysShapes>();
fb.pipeline(lPhSh.spawnRequest) .parent(plScn.update);
fb.pipeline(lPhSh.spawnedEnts) .parent(plScn.update);
fb.pipeline(lPhSh.ownedEnts) .parent(plScn.update);
fb.data(dPhSh.physShapes).emplace< ACtxPhysShapes >( ACtxPhysShapes{ .m_materialId = materialId } );
fb.task()
.name ("Schedule Shape spawn")
.schedules ({lPhSh.spawnRequest(Schedule_)})
.sync_with ({plScn.update(Run)})
.args ({ dPhSh.physShapes })
.func([] (ACtxPhysShapes& rPhysShapes) noexcept -> TaskActions
{
return rPhysShapes.m_spawnRequest.empty() ? TaskAction::Cancel : TaskActions{};
});
// ...
materialId
here doesn't have a definition. I haven't quite figured out the 'best' way to pass config data down to feature setups.src/testapp/scenarios.cpp
:
auto ctxBuilder = rTestApp.builder().create_context();
rTopData.m_currentScene = ctxBuilder.context_id();
ctxBuilder.add_feature(&ft_scene);
ctxBuilder.add_feature(&ft_scene_common);
ctxBuilder.add_feature(&ft_physics);
ctxBuilder.add_feature(&ft_phys_shapes);
// ...
putting this here to not be so dependent on discord.
A couple iterations later: https://godbolt.org/z/Gb3GvPqar
C++ metaprogramming is inherently a little cursed but that's that.
I'm not expecting many people to understand how the template nonsense works (I put quite a bit of effort into making it actually readable, though it would be a fun challenge to dive into).
I mostly care about the user code below, which can drastically reduce the amount of boilerplate in testapp.
Calls to fb.use_interface<...>
and fb.implement_interface<...>
from the previous comment won't be needed anymore as function arguments are instead read to determine dependencies:
auto const gc_ftrPhysShapes = feature_def([] (FeatureBuilder& rBuilder, Implement<FIPhysShapes> phySh, DependOn<FIScene> scn, DependOn<FIGenericPhysics> phys)
{
// Initialize values of TopData (top_emplace),
// setup pipelines (rBuilder.pipeline(...).parent(...)),
// and setup tasks here
});
quoting my previous comment:
allow specifying dependencies to Feature Interfaces elsewhere, so they're accessible before this function is even called
A few related things are required that are closely related:
A) Allow changing scenarios while the window is open
Requires a bit of refactoring to scenarios.
Sessions for keeping the window open (setup_window_app and setup_magnum) are already managed separately, found in main.cpp.
https://github.com/TheOpenSpaceProgram/osp-magnum/blob/c42ed3195318c3925373b5107636aaac69d17305/src/testapp/main.cpp#L270-L271
This means that scene-related sessions (setup_scene, setup_common_scene, setup_scene_renderer, setup_magnum_scene, setup_camera_ctrl, etc...) can be closed and new ones can be loaded in while the window stays open.
When a session is requested to change...
B) Have some API to open new sessions
Imagine starting with just a main menu session that only uses UI. The user can then select and load up a flight scenario, launching new sessions (this is why sessions are called sessions).
Challenges:
In a more finalized product, a list of flights scenes, their sessions, and additional data about them (eg: associations with the universe) can be stored somewhere at the top-level of the application. Renderers can then be optionally assigned to them for the application's compositor function to call into.
C) Automatic runtime Session dependencies
Right now, dependencies between sessions are intended to be easy to write and copy-paste around manually, relying on lists of variable names using macros. None of this can really be reconfigured to create custom scenarios at runtime.
Note how similar the physics test scenario and the vehicles test scenario is. You can just put
if
statements around the vehicle-related setup functions to turn the vehicles test scenario into the physics test scenario conditionally. It's possible to make one mega-sized scenario instead individual physics/vehicle/universe ones, but this can probably be done in a smarter way for the "more finalized product."Ideally, the interface for this would be to just list off sessions, and dependencies will be automatically resolved. To do this, we need some way to identify/lookup sessions and determine what dependencies they require (make a trait system :3). A bunch of tables of data works for sure, but part of the problem is how to write a nice interface around it.
This might require a full rewrite of all the session stuff. Sessions are just groups of tasks, data, and pipelines, and maybe dealing with them individually 'per-scene' and removing sessions entirely may be easier for an automated solution. we'll see.