diff --git a/src/adera/Shaders/PlumeShader.cpp b/src/adera/Shaders/PlumeShader.cpp index 7fde5dd2..7b885820 100644 --- a/src/adera/Shaders/PlumeShader.cpp +++ b/src/adera/Shaders/PlumeShader.cpp @@ -106,7 +106,7 @@ void PlumeShader::assign_plumes( { for (ActiveEnt ent : entities) { - rStorage.emplace(ent, EntityToDraw{&draw_plume, {&rData} }); + //rStorage.emplace(ent, EntityToDraw{&draw_plume, {&rData} }); } } diff --git a/src/adera/machines/links.cpp b/src/adera/machines/links.cpp index 0747ed2d..eb5fd6e2 100644 --- a/src/adera/machines/links.cpp +++ b/src/adera/machines/links.cpp @@ -32,10 +32,6 @@ using osp::link::MachTypeId; namespace adera { -MachTypeId const gc_mtUserCtrl = MachTypeReg_t::create(); -MachTypeId const gc_mtMagicRocket = MachTypeReg_t::create(); -MachTypeId const gc_mtRcsDriver = MachTypeReg_t::create(); - float thruster_influence(Vector3 const pos, Vector3 const dir, Vector3 const cmdLin, Vector3 const cmdAng) noexcept { using Magnum::Math::cross; diff --git a/src/adera/machines/links.h b/src/adera/machines/links.h index 68abe65b..aa70da3b 100644 --- a/src/adera/machines/links.h +++ b/src/adera/machines/links.h @@ -35,9 +35,9 @@ using osp::link::gc_ntSigFloat; using osp::link::gc_sigIn; using osp::link::gc_sigOut; -extern osp::link::MachTypeId const gc_mtUserCtrl; -extern osp::link::MachTypeId const gc_mtMagicRocket; -extern osp::link::MachTypeId const gc_mtRcsDriver; +inline osp::link::MachTypeId const gc_mtUserCtrl = osp::link::MachTypeReg_t::create(); +inline osp::link::MachTypeId const gc_mtMagicRocket = osp::link::MachTypeReg_t::create(); +inline osp::link::MachTypeId const gc_mtRcsDriver = osp::link::MachTypeReg_t::create(); constexpr osp::Vector3 gc_rocketForward{0.0f, 0.0f, 1.0f}; diff --git a/src/newtondynamics_physics/SysNewton.cpp b/src/newtondynamics_physics/SysNewton.cpp index 37bef6a3..ce13288c 100644 --- a/src/newtondynamics_physics/SysNewton.cpp +++ b/src/newtondynamics_physics/SysNewton.cpp @@ -55,7 +55,6 @@ using namespace Magnum::Math::Literals; using osp::phys::EShape; -using osp::active::acomp_view_t; using osp::active::acomp_storage_t; using osp::active::ActiveEnt; diff --git a/src/osp/Active/SysPrefabInit.cpp b/src/osp/Active/SysPrefabInit.cpp index ece0f0f7..a3461b7f 100644 --- a/src/osp/Active/SysPrefabInit.cpp +++ b/src/osp/Active/SysPrefabInit.cpp @@ -116,10 +116,12 @@ void SysPrefabInit::init_drawing( Resources& rResources, ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, - std::optional material) noexcept + std::optional material) noexcept { - auto itPfEnts = std::begin(rPrefabInit.m_ents); + auto itPfEnts = rPrefabInit.m_ents.begin(); + // stupidly make drawIDs first + // TODO: separate this into another step. for (TmpPrefabInitBasic const& rPfBasic : rPrefabInit.m_basicIn) { auto const &rImportData = rResources.data_get( @@ -131,13 +133,53 @@ void SysPrefabInit::init_drawing( for (std::size_t i = 0; i < objects.size(); ++i) { + int const meshImportId = rImportData.m_objMeshes[objects[i]]; + if (meshImportId == -1) + { + continue; + } + ActiveEnt const ent = (*itPfEnts)[i]; + rDrawing.m_activeToDraw[ent] = rDrawing.m_drawIds.create(); + } + + ++itPfEnts; + } + + // then resize containers + rDrawing.resize_draw(); + + itPfEnts = rPrefabInit.m_ents.begin(); - // TODO: Don't actually do this. This marks every entity as drawable, - // which considers them for draw transformations. - // Only set drawable for entities that have a mesh or is an - // ancestor of an entity with a mesh. - rDrawing.m_drawable.set(std::size_t(ent)); + for (TmpPrefabInitBasic const& rPfBasic : rPrefabInit.m_basicIn) + { + auto const &rImportData = rResources.data_get( + gc_importer, rPfBasic.m_importerRes); + auto const &rPrefabData = rResources.data_get( + gc_importer, rPfBasic.m_importerRes); + + auto const ents = ArrayView{*itPfEnts}; + auto const objects = lgrn::Span{rPrefabData.m_prefabs[rPfBasic.m_prefabId]}; + auto const parents = lgrn::Span{rPrefabData.m_prefabParents[rPfBasic.m_prefabId]}; + + // All ancestors of each entity that has a mesh + auto const needs_draw_transform + = [&parents, &ents, &rDrawing, &needDrawTf = rDrawing.m_needDrawTf] + (auto && self, int const object, ActiveEnt const ent) noexcept -> void + { + needDrawTf.set(std::size_t(ent)); + + int const parentObj = parents[object]; + + if (parentObj != -1) + { + self(self, parentObj, ents[parentObj]); + } + }; + + for (std::size_t i = 0; i < objects.size(); ++i) + { + ActiveEnt const ent = (*itPfEnts)[i]; // Check if object has mesh int const meshImportId = rImportData.m_objMeshes[objects[i]]; @@ -146,10 +188,14 @@ void SysPrefabInit::init_drawing( continue; } + needs_draw_transform(needs_draw_transform, objects[i], ent); + + DrawEnt const drawEnt = rDrawing.m_activeToDraw[ent]; + osp::ResId const meshRes = rImportData.m_meshes[meshImportId]; MeshId const meshId = SysRender::own_mesh_resource(rDrawing, rDrawingRes, rResources, meshRes); - rDrawing.m_mesh.emplace(ent, rDrawing.m_meshRefCounts.ref_add(meshId)); - rDrawing.m_meshDirty.push_back(ent); + rDrawing.m_mesh[drawEnt] = rDrawing.m_meshRefCounts.ref_add(meshId); + rDrawing.m_meshDirty.push_back(drawEnt); int const matImportId = rImportData.m_objMaterials[objects[i]]; @@ -162,23 +208,22 @@ void SysPrefabInit::init_drawing( { osp::ResId const texRes = rImportData.m_textures[baseColor]; TexId const texId = SysRender::own_texture_resource(rDrawing, rDrawingRes, rResources, texRes); - rDrawing.m_diffuseTex.emplace(ent, rDrawing.m_texRefCounts.ref_add(texId)); - rDrawing.m_diffuseDirty.push_back(ent); + rDrawing.m_diffuseTex[drawEnt] = rDrawing.m_texRefCounts.ref_add(texId); + rDrawing.m_diffuseDirty.push_back(drawEnt); } - } - rDrawing.m_opaque.emplace(ent); - rDrawing.m_visible.emplace(ent); + rDrawing.m_drawBasic[drawEnt] = { .m_opaque = true, .m_transparent = false }; + rDrawing.m_visible.set(std::size_t(drawEnt)); if (material.has_value()) { - material.value().m_rEnts.set(std::size_t(ent)); - material.value().m_rDirty.push_back(ent); + material.value().m_ents.set(std::size_t(drawEnt)); + material.value().m_dirty.push_back(drawEnt); } } - std::advance(itPfEnts, 1); + ++itPfEnts; } } diff --git a/src/osp/Active/SysPrefabInit.h b/src/osp/Active/SysPrefabInit.h index 3dda256b..bbe1a19e 100644 --- a/src/osp/Active/SysPrefabInit.h +++ b/src/osp/Active/SysPrefabInit.h @@ -92,7 +92,7 @@ class SysPrefabInit Resources& rResources, ACtxDrawing& rDrawing, ACtxDrawingRes& rCtxDrawRes, - std::optional material) noexcept; + std::optional material) noexcept; static void init_physics( ACtxPrefabInit const& rPrefabInit, diff --git a/src/osp/Active/SysRender.cpp b/src/osp/Active/SysRender.cpp index f541f8b1..86e9e8c6 100644 --- a/src/osp/Active/SysRender.cpp +++ b/src/osp/Active/SysRender.cpp @@ -91,47 +91,70 @@ void SysRender::set_dirty_all(ACtxDrawing &rCtxDrawing) { using osp::active::active_sparse_set_t; - // Set all meshs dirty - auto &rMeshSet = static_cast(rCtxDrawing.m_mesh); - rCtxDrawing.m_meshDirty.assign(std::begin(rMeshSet), std::end(rMeshSet)); - // Set all textures dirty - auto &rDiffSet = static_cast(rCtxDrawing.m_diffuseTex); - rCtxDrawing.m_diffuseDirty.assign(std::begin(rMeshSet), std::end(rMeshSet)); -} + for (std::size_t const drawEntInt : rCtxDrawing.m_drawIds.bitview().zeros()) + { + auto const drawEnt = DrawEnt(drawEntInt); -void SysRender::clear_dirty_all(ACtxDrawing& rCtxDrawing) -{ - rCtxDrawing.m_meshDirty.clear(); - rCtxDrawing.m_diffuseDirty.clear(); + // Set all meshs dirty + if (rCtxDrawing.m_mesh[drawEnt] != lgrn::id_null()) + { + rCtxDrawing.m_meshDirty.push_back(drawEnt); + } + + // Set all textures dirty + if (rCtxDrawing.m_diffuseTex[drawEnt] != lgrn::id_null()) + { + rCtxDrawing.m_diffuseDirty.push_back(drawEnt); + } + } + + for (std::size_t const materialInt : rCtxDrawing.m_materialIds.bitview().zeros()) + { + Material &mat = rCtxDrawing.m_materials[MaterialId(materialInt)]; + for (std::size_t const entInt : mat.m_ents.ones()) + { + mat.m_dirty.push_back(DrawEnt(entInt)); + } + } } void SysRender::update_draw_transforms_recurse( ACtxSceneGraph const& rScnGraph, + KeyedVec const& activeToDraw, acomp_storage_t const& rTf, - acomp_storage_t& rDrawTf, - EntSet_t const& rDrawable, + DrawTransforms_t& rDrawTf, + ActiveEntSet_t const& needDrawTf, ActiveEnt ent, Matrix4 const& parentTf, bool root) { - Matrix4 const& entTf = rTf.get(ent).m_transform; - Matrix4 const& entDrawTf = root ? (entTf) : (parentTf * entTf); + Matrix4 const& entTf = rTf.get(ent).m_transform; + Matrix4 const& entDrawTf = root ? (entTf) : (parentTf * entTf); - if (rDrawTf.contains(ent)) + if (DrawEnt const drawEnt = activeToDraw[ent]; + drawEnt != lgrn::id_null()) { - rDrawTf.get(ent) = entDrawTf; + rDrawTf[drawEnt] = entDrawTf; } for (ActiveEnt entChild : SysSceneGraph::children(rScnGraph, ent)) { - if (rDrawable.test(std::size_t(entChild))) + if (needDrawTf.test(std::size_t(entChild))) { - update_draw_transforms_recurse(rScnGraph, rTf, rDrawTf, rDrawable, entChild, entDrawTf, false); + update_draw_transforms_recurse(rScnGraph, activeToDraw, rTf, rDrawTf, needDrawTf, entChild, entDrawTf, false); } } +} + - //parentTf. +MeshIdOwner_t SysRender::add_drawable_mesh(ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, Resources& rResources, PkgId const pkg, std::string_view const name) +{ + ResId const res = rResources.find(restypes::gc_mesh, pkg, name); + assert(res != lgrn::id_null()); + MeshId const meshId = SysRender::own_mesh_resource(rDrawing, rDrawingRes, rResources, res); + return rDrawing.m_meshRefCounts.ref_add(meshId); } + diff --git a/src/osp/Active/SysRender.h b/src/osp/Active/SysRender.h index 28e5f058..c5e8007a 100644 --- a/src/osp/Active/SysRender.h +++ b/src/osp/Active/SysRender.h @@ -25,12 +25,15 @@ #pragma once #include "drawing.h" +#include "basic.h" #include namespace osp::active { +using DrawTransforms_t = KeyedVec; + /** * @brief View and Projection matrix */ @@ -62,10 +65,10 @@ struct EntityToDraw * @param UserData_t [in] Non-owning user data */ using ShaderDrawFnc_t = void (*)( - ActiveEnt, ViewProjMatrix const&, UserData_t) noexcept; + DrawEnt, ViewProjMatrix const&, UserData_t) noexcept; constexpr void operator()( - ActiveEnt ent, + DrawEnt ent, ViewProjMatrix const& viewProj) const noexcept { m_draw(ent, viewProj, m_data); @@ -88,7 +91,7 @@ struct EntityToDraw */ struct RenderGroup { - using Storage_t = entt::storage_type::type; + using Storage_t = entt::basic_storage; using ArrayView_t = Corrade::Containers::ArrayView; /** @@ -173,9 +176,10 @@ class SysRender template static void update_draw_transforms( ACtxSceneGraph const& rScnGraph, + KeyedVec const& activeToDraw, acomp_storage_t const& transform, - acomp_storage_t& rDrawTf, - EntSet_t const& rDrawable, + DrawTransforms_t& rDrawTf, + ActiveEntSet_t const& useDrawTf, IT_T first, ITB_T const& last); @@ -186,27 +190,25 @@ class SysRender */ static void set_dirty_all(ACtxDrawing& rCtxDrawing); - /** - * @brief Clear all dirty flags/vectors - * - * @param rCtxDrawing [ref] Drawing data - */ - static void clear_dirty_all(ACtxDrawing& rCtxDrawing); - template static void update_delete_drawing( - ACtxDrawing& rCtxDraw, IT_T first, IT_T const& last); + ACtxDrawing& rCtxDraw, IT_T const& first, IT_T const& last); template static void update_delete_groups( ACtxRenderGroups& rCtxGroups, IT_T first, IT_T const& last); + static MeshIdOwner_t add_drawable_mesh(ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, Resources& rResources, PkgId const pkg, std::string_view const name); + + static constexpr decltype(auto) gen_drawable_mesh_adder(ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, Resources& rResources, PkgId const pkg); + private: static void update_draw_transforms_recurse( ACtxSceneGraph const& rScnGraph, + KeyedVec const& activeToDraw, acomp_storage_t const& rTf, - acomp_storage_t& rDrawTf, - EntSet_t const& rDrawable, + DrawTransforms_t& rDrawTf, + ActiveEntSet_t const& useDrawTf, ActiveEnt ent, Matrix4 const& parentTf, bool root); @@ -230,9 +232,10 @@ void SysRender::assure_draw_transforms( template void SysRender::update_draw_transforms( ACtxSceneGraph const& rScnGraph, + KeyedVec const& activeToDraw, acomp_storage_t const& rTf, - acomp_storage_t& rDrawTf, - EntSet_t const& rDrawable, + DrawTransforms_t& rDrawTf, + ActiveEntSet_t const& needDrawTf, IT_T first, ITB_T const& last) { @@ -240,7 +243,7 @@ void SysRender::update_draw_transforms( while (first != last) { - update_draw_transforms_recurse(rScnGraph, rTf, rDrawTf, rDrawable, *first, identity, true); + update_draw_transforms_recurse(rScnGraph, activeToDraw, rTf, rDrawTf, needDrawTf, *first, identity, true); std::advance(first, 1); } @@ -249,36 +252,25 @@ void SysRender::update_draw_transforms( template void remove_refcounted( - ActiveEnt const ent, STORAGE_T &rStorage, REFCOUNT_T &rRefcount) + DrawEnt const ent, STORAGE_T &rStorage, REFCOUNT_T &rRefcount) { - if (rStorage.contains(ent)) + auto &rOwner = rStorage[ent]; + if (rOwner.has_value()) { - auto &rOwner = rStorage.get(ent); - if (rOwner.has_value()) - { - rRefcount.ref_release(std::move(rOwner)); - } - rStorage.erase(ent); + rRefcount.ref_release(std::move(rOwner)); } } template void SysRender::update_delete_drawing( - ACtxDrawing& rCtxDraw, IT_T first, IT_T const& last) + ACtxDrawing& rCtxDraw, IT_T const& first, IT_T const& last) { - while (first != last) + for (auto it = first; it != last; std::advance(it, 1)) { - ActiveEnt const ent = *first; - rCtxDraw.m_opaque .remove(ent); - rCtxDraw.m_transparent .remove(ent); - rCtxDraw.m_visible .remove(ent); - - // Textures and meshes are reference counted - remove_refcounted(ent, rCtxDraw.m_diffuseTex, rCtxDraw.m_texRefCounts); - remove_refcounted(ent, rCtxDraw.m_mesh, rCtxDraw.m_meshRefCounts); - + DrawEnt const drawEnt = *it; - std::advance(first, 1); + remove_refcounted(drawEnt, rCtxDraw.m_diffuseTex, rCtxDraw.m_texRefCounts); + remove_refcounted(drawEnt, rCtxDraw.m_mesh, rCtxDraw.m_meshRefCounts); } } @@ -296,4 +288,14 @@ void SysRender::update_delete_groups( } } +constexpr decltype(auto) SysRender::gen_drawable_mesh_adder(ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, Resources& rResources, PkgId const pkg) +{ + return [&rDrawing, &rDrawingRes, &rResources, pkg] (std::string_view const name) -> MeshIdOwner_t + { + return add_drawable_mesh(rDrawing, rDrawingRes, rResources, pkg, name); + }; +} + + + } // namespace osp::active diff --git a/src/osp/Active/SysSceneGraph.h b/src/osp/Active/SysSceneGraph.h index f0b2b7ee..6ce9f15b 100644 --- a/src/osp/Active/SysSceneGraph.h +++ b/src/osp/Active/SysSceneGraph.h @@ -160,6 +160,12 @@ class SysSceneGraph template static void cut(ACtxSceneGraph& rScnGraph, ITA_T first, ITB_T const& last); + /** + * @brief Add multiple entities and their descendents to a delete queue + */ + template + static void queue_delete_entities(ACtxSceneGraph& rScnGraph, ActiveEntVec_t &rDelete, ITA_T const& first, ITB_T const& last); + private: static void do_delete(ACtxSceneGraph& rScnGraph); @@ -186,5 +192,20 @@ void SysSceneGraph::cut(ACtxSceneGraph& rScnGraph, ITA_T first, ITB_T const& las do_delete(rScnGraph); } +template +void SysSceneGraph::queue_delete_entities(ACtxSceneGraph& rScnGraph, ActiveEntVec_t &rDelete, ITA_T const& first, ITB_T const& last) +{ + std::for_each(first, last, [&] (ActiveEnt const ent) + { + rDelete.push_back(ent); + for (ActiveEnt const descendent : SysSceneGraph::descendants(rScnGraph, ent)) + { + rDelete.push_back(descendent); + } + }); + + SysSceneGraph::cut(rScnGraph, first, last); +} + } // namespace osp::active diff --git a/src/osp/Active/activetypes.h b/src/osp/Active/activetypes.h index 30e8a006..4765a7a3 100644 --- a/src/osp/Active/activetypes.h +++ b/src/osp/Active/activetypes.h @@ -24,6 +24,8 @@ */ #pragma once +#include "../bitvector.h" // for osp::BitVector_t + // IWYU pragma: begin_exports #include // for entt::id_type @@ -41,44 +43,26 @@ namespace osp::active { -enum class ActiveEnt: entt::id_type {}; - -} // namespace osp::active - - -// Specialize entt::storage_traits to disable signals for storage that uses -// ActiveEnt as entities -template -struct entt::storage_type -{ - using type = basic_storage; -}; - +enum class ActiveEnt: uint32_t { }; -namespace osp::active -{ +using ActiveEntVec_t = std::vector; +using ActiveEntSet_t = BitVector_t; +using active_sparse_set_t = entt::basic_sparse_set; -inline constexpr unsigned gc_heir_physics_level = 1; +template +using acomp_storage_t = typename entt::basic_storage; -using ActiveReg_t = lgrn::IdRegistryStl; +//----------------------------------------------------------------------------- -using EntVector_t = std::vector; -using EntSet_t = lgrn::BitView< std::vector >; +enum class DrawEnt : uint32_t { }; -struct EntSetPair -{ - EntSet_t &m_rEnts; - EntVector_t &m_rDirty; -}; +using DrawEntVec_t = std::vector; +using DrawEntSet_t = BitVector_t; -using active_sparse_set_t = entt::basic_sparse_set; -template -using acomp_storage_t = typename entt::storage_type::type; +enum class MaterialId : uint32_t { }; -template -using acomp_view_t = entt::basic_view, entt::exclude_t<>>; } // namespace osp::active diff --git a/src/osp/Active/basic.h b/src/osp/Active/basic.h index 66a37a2c..430d7e92 100644 --- a/src/osp/Active/basic.h +++ b/src/osp/Active/basic.h @@ -54,9 +54,10 @@ using TreePos_t = uint32_t; struct ACtxSceneGraph { - // Tree structure stored using an array of descendant count in parallel with - // identificaton (entities) - // A(B(C(D)), E(F(G(H,I)))) -> [A,B,C,D,E,F,G,H,I] and [8,2,1,0,4,3,2,0,0] + // N-ary tree structure represented as an array of descendant counts. Each node's subtree of + // descendants is positioned directly after it within the array. + // Example for tree structure "A( B(C(D)), E(F(G(H,I))) )" + // * Descendant Count array: [A:8, B:2, C:1, D:0, E:4, F:3, G:2, H:0, I:0] std::vector m_treeToEnt{lgrn::id_null()}; std::vector m_treeDescendants{std::initializer_list{0}}; @@ -79,17 +80,15 @@ struct ACtxSceneGraph */ struct ACtxBasic { - ACtxSceneGraph m_scnGraph; + lgrn::IdRegistryStl m_activeIds; - acomp_storage_t m_transform; - acomp_storage_t m_name; + ACtxSceneGraph m_scnGraph; + acomp_storage_t m_transform; }; template void update_delete_basic(ACtxBasic &rCtxBasic, IT_T first, IT_T const& last) { - rCtxBasic.m_name .remove(first, last); - while (first != last) { ActiveEnt const ent = *first; diff --git a/src/osp/Active/drawing.h b/src/osp/Active/drawing.h index 6497d49c..6f93addc 100644 --- a/src/osp/Active/drawing.h +++ b/src/osp/Active/drawing.h @@ -25,35 +25,34 @@ #pragma once #include "activetypes.h" -#include "basic.h" -#include "../types.h" + +#include "../bitvector.h" #include "../id_map.h" +#include "../keyed_vector.h" +#include "../types.h" #include "../Resource/resourcetypes.h" #include #include -#include #include namespace osp::active { -/** - * @brief An object that is completely opaque - */ -struct ACompOpaque {}; -/** - * @brief An object with transparency - */ -struct ACompTransparent {}; +struct BasicDrawProps +{ + bool m_opaque:1 { false }; + bool m_transparent:1 { false }; +}; -/** - * @brief Visibility state of this object - */ -struct ACompVisible {}; +struct Material +{ + DrawEntSet_t m_ents; + DrawEntVec_t m_dirty; +}; /** * @brief Mesh that describes the appearance of an entity @@ -69,7 +68,6 @@ enum class MeshId : uint32_t { }; */ enum class TexId : uint32_t { }; -struct ACompColor : Magnum::Color4 {}; using MeshRefCount_t = lgrn::IdRefCount; using MeshIdOwner_t = MeshRefCount_t::Owner_t; @@ -83,27 +81,49 @@ using TexIdOwner_t = TexRefCount_t::Owner_t; struct ACtxDrawing { - // Drawing Components - EntSet_t m_drawable; - acomp_storage_t m_opaque; - acomp_storage_t m_transparent; - acomp_storage_t m_visible; - acomp_storage_t m_color; + void resize_draw() + { + std::size_t const size = m_drawIds.capacity(); + + bitvector_resize(m_visible, size); + m_drawBasic .resize(size); + m_color .resize(size); + m_diffuseTex .resize(size); + m_mesh .resize(size); + } + + void resize_active(std::size_t const size) + { + bitvector_resize(m_needDrawTf, size); + m_activeToDraw.resize(size, lgrn::id_null()); + } + + lgrn::IdRegistryStl m_drawIds; + + DrawEntSet_t m_visible; + KeyedVec m_drawBasic; + KeyedVec m_color; + + DrawEntSet_t m_needDrawTf; + KeyedVec m_activeToDraw; // Scene-space Meshes - lgrn::IdRegistry m_meshIds; + lgrn::IdRegistryStl m_meshIds; MeshRefCount_t m_meshRefCounts; // Scene-space Textures - lgrn::IdRegistry m_texIds; + lgrn::IdRegistryStl m_texIds; TexRefCount_t m_texRefCounts; - // Meshes and textures assigned to ActiveEnts - acomp_storage_t m_diffuseTex; - std::vector m_diffuseDirty; + // Meshes and textures assigned to DrawEnts + KeyedVec m_diffuseTex; + std::vector m_diffuseDirty; + + KeyedVec m_mesh; + std::vector m_meshDirty; - acomp_storage_t m_mesh; - std::vector m_meshDirty; + lgrn::IdRegistryStl m_materialIds; + KeyedVec m_materials; }; /** diff --git a/src/osp/Active/opengl/SysRenderGL.cpp b/src/osp/Active/opengl/SysRenderGL.cpp index 3045d4d7..f198cc05 100644 --- a/src/osp/Active/opengl/SysRenderGL.cpp +++ b/src/osp/Active/opengl/SysRenderGL.cpp @@ -111,14 +111,13 @@ void SysRenderGL::setup_context(RenderGL& rCtxGl) } } -void SysRenderGL::sync_scene_resources( - const ACtxDrawingRes &rCtxDrawRes, - Resources &rResources, - RenderGL &rRenderGl) +void SysRenderGL::compile_resource_textures( + ACtxDrawingRes const& rCtxDrawRes, + Resources& rResources, + RenderGL& rRenderGl) { // TODO: Eventually have dirty flags instead of checking every entry. - // Compile required texture resources for ([[maybe_unused]] auto const & [_, scnOwner] : rCtxDrawRes.m_texToRes) { ResId const texRes = scnOwner.value(); @@ -168,8 +167,15 @@ void SysRenderGL::sync_scene_resources( .setStorage(1, textureFormat(imgData.format()), imgData.size()) .setSubImage(0, {}, imgData); } +} + +void SysRenderGL::compile_resource_meshes( + ACtxDrawingRes const& rCtxDrawRes, + Resources& rResources, + RenderGL& rRenderGl) +{ + // TODO: Eventually have dirty flags instead of checking every entry. - // Compile required mesh resources for ([[maybe_unused]] auto const & [_, scnOwner] : rCtxDrawRes.m_meshToRes) { ResId const meshRes = scnOwner.value(); @@ -203,114 +209,102 @@ void SysRenderGL::sync_scene_resources( } } -void SysRenderGL::assign_meshes( - acomp_storage_t const& cmpMeshIds, - IdMap_t const& meshToRes, - std::vector const& entsDirty, - acomp_storage_t& rCmpMeshGl, - RenderGL& rRenderGl) +void SysRenderGL::sync_drawent_mesh( + DrawEnt const ent, + KeyedVec const& cmpMeshIds, + IdMap_t const& meshToRes, + MeshGlEntStorage_t& rCmpMeshGl, + RenderGL& rRenderGl) { - for (ActiveEnt const ent : entsDirty) + ACompMeshGl &rEntMeshGl = rCmpMeshGl[ent]; + MeshIdOwner_t const& entMeshScnId = cmpMeshIds[ent]; + + // Make sure dirty entity has a MeshId component + if (entMeshScnId.has_value()) + { + // Check if scene mesh ID is properly synchronized + if (rEntMeshGl.m_scnId == entMeshScnId) + { + return; // No changes needed + } + + rEntMeshGl.m_scnId = entMeshScnId; + + // Check if MeshId is associated with a resource + if (auto const& foundIt = meshToRes.find(entMeshScnId); + foundIt != meshToRes.end()) + { + ResId const meshResId = foundIt->second; + + // Mesh should have been loaded beforehand, assign it! + rEntMeshGl.m_glId = rRenderGl.m_resToMesh.at(meshResId); + } + else + { + OSP_LOG_WARN("No mesh data found for Mesh {} from Entity {}", + std::size_t(entMeshScnId), std::size_t(ent)); + } + } + else { - // Make sure dirty entity has a MeshId component - if (cmpMeshIds.contains(ent)) + if (rEntMeshGl.m_glId != lgrn::id_null()) { - MeshId const entMeshScnId = cmpMeshIds.get(ent); - - ACompMeshGl &rEntMeshGl = rCmpMeshGl.contains(ent) - ? rCmpMeshGl.get(ent) - : rCmpMeshGl.emplace(ent); - - // Check if scene mesh ID is properly synchronized - if (rEntMeshGl.m_scnId == entMeshScnId) - { - continue; // No changes needed - } - - rEntMeshGl.m_scnId = entMeshScnId; - - // Check if MeshId is associated with a resource - if (auto const& foundIt = meshToRes.find(entMeshScnId); - foundIt != meshToRes.end()) - { - ResId const meshResId = foundIt->second; - - // Mesh should have been loaded beforehand, assign it! - rEntMeshGl.m_glId = rRenderGl.m_resToMesh.at(meshResId); - } - else - { - OSP_LOG_WARN("No mesh data found for Mesh {} from Entity {}", - std::size_t(entMeshScnId), std::size_t(ent)); - } + // ACompMesh removed, remove ACompMeshGL too + rEntMeshGl = {}; } else { - if (rCmpMeshGl.contains(ent)) - { - // ACompMesh removed, remove ACompMeshGL too - rCmpMeshGl.erase(ent); - } - else - { - // Why is this entity here? - } + // Why is this entity here? } } } -void SysRenderGL::assign_textures( - acomp_storage_t const& cmpTexIds, - IdMap_t const& texToRes, - std::vector const& entsDirty, - acomp_storage_t& rCmpTexGl, - RenderGL& rRenderGl) +void SysRenderGL::sync_drawent_texture( + DrawEnt const ent, + KeyedVec const& cmpTexIds, + IdMap_t const& texToRes, + TexGlEntStorage_t& rCmpTexGl, + RenderGL& rRenderGl) { - for (ActiveEnt const ent : entsDirty) + ACompTexGl &rEntTexGl = rCmpTexGl[ent]; + TexIdOwner_t const& entTexScnId = cmpTexIds[ent]; + + // Make sure dirty entity has a MeshId component + if (entTexScnId.has_value()) + { + // Check if scene mesh ID is properly synchronized + if (rEntTexGl.m_scnId == entTexScnId) + { + return; // No changes needed + } + + rEntTexGl.m_scnId = entTexScnId; + + // Check if MeshId is associated with a resource + if (auto const& foundIt = texToRes.find(entTexScnId); + foundIt != texToRes.end()) + { + ResId const texResId = foundIt->second; + + // Mesh should have been loaded beforehand, assign it! + rEntTexGl.m_glId = rRenderGl.m_resToTex.at(texResId); + } + else + { + OSP_LOG_WARN("No mesh data found for Mesh {} from Entity {}", + std::size_t(entMeshScnId), std::size_t(ent)); + } + } + else { - // Make sure dirty entity has a MeshId component - if (cmpTexIds.contains(ent)) + if (rEntTexGl.m_glId != lgrn::id_null()) { - TexId const entTexScnId = cmpTexIds.get(ent); - - ACompTexGl &rEntTexGl = rCmpTexGl.contains(ent) - ? rCmpTexGl.get(ent) - : rCmpTexGl.emplace(ent); - - // Check if scene mesh ID is properly synchronized - if (rEntTexGl.m_scnId == entTexScnId) - { - continue; // No changes needed - } - - rEntTexGl.m_scnId = entTexScnId; - - // Check if MeshId is associated with a resource - if (auto const& foundIt = texToRes.find(entTexScnId); - foundIt != texToRes.end()) - { - ResId const texResId = foundIt->second; - - // Mesh should have been loaded beforehand, assign it! - rEntTexGl.m_glId = rRenderGl.m_resToTex.at(texResId); - } - else - { - OSP_LOG_WARN("No mesh data found for Mesh {} from Entity {}", - std::size_t(entMeshScnId), std::size_t(ent)); - } + // ACompMesh removed, remove ACompMeshGL too + rEntTexGl = {}; } else { - if (rCmpTexGl.contains(ent)) - { - // ACompMesh removed, remove ACompMeshGL too - rCmpTexGl.erase(ent); - } - else - { - // Why is this entity here? - } + // Why is this entity here? } } } @@ -352,7 +346,7 @@ void SysRenderGL::clear_resource_owners(RenderGL& rRenderGl, Resources& rResourc void SysRenderGL::render_opaque( RenderGroup const& group, - acomp_storage_t const& visible, + DrawEntSet_t const& visible, ViewProjMatrix const& viewProj) { using Magnum::GL::Renderer; @@ -367,7 +361,7 @@ void SysRenderGL::render_opaque( void SysRenderGL::render_transparent( RenderGroup const& group, - acomp_storage_t const& visible, + DrawEntSet_t const& visible, ViewProjMatrix const& viewProj) { using Magnum::GL::Renderer; @@ -388,12 +382,12 @@ void SysRenderGL::render_transparent( void SysRenderGL::draw_group( RenderGroup const& group, - acomp_storage_t const& visible, + DrawEntSet_t const& visible, ViewProjMatrix const& viewProj) { for (auto const& [ent, toDraw] : group.view().each()) { - if (visible.contains(ent)) + if (visible.test(std::size_t(ent))) { toDraw(ent, viewProj); } diff --git a/src/osp/Active/opengl/SysRenderGL.h b/src/osp/Active/opengl/SysRenderGL.h index 95ca0009..3a5f2537 100644 --- a/src/osp/Active/opengl/SysRenderGL.h +++ b/src/osp/Active/opengl/SysRenderGL.h @@ -94,17 +94,17 @@ struct ACompMeshGl MeshGlId m_glId {lgrn::id_null()}; }; -using ACompMeshGlStorage_t = acomp_storage_t; -using ACompTexGlStorage_t = acomp_storage_t; +using MeshGlEntStorage_t = KeyedVec; +using TexGlEntStorage_t = KeyedVec; /** * @brief OpenGL specific rendering components for rendering a scene */ struct ACtxSceneRenderGL { - ACompMeshGlStorage_t m_meshId; - ACompTexGlStorage_t m_diffuseTexId; - acomp_storage_t m_drawTransform; + MeshGlEntStorage_t m_meshId; + TexGlEntStorage_t m_diffuseTexId; + DrawTransforms_t m_drawTransform; }; /** @@ -136,33 +136,59 @@ class SysRenderGL static void clear_resource_owners(RenderGL& rRenderGl, Resources& rResources); /** - * @brief Compile required meshes and textures resources used by a scene + * @brief Compile GPU-side TexGlIds for textures loaded from a Resource (TexId + ResId) * * @param rCtxDrawRes [in] Resources used by the scene - * @param rResources [ref] Application Resources shared with the scene. - * New resource owners may be created. + * @param rResources [ref] Application Resources shared with the scene. New resource owners may be created. * @param rRenderGl [ref] Renderer state */ - static void sync_scene_resources( + static void compile_resource_textures( ACtxDrawingRes const& rCtxDrawRes, Resources& rResources, RenderGL& rRenderGl); /** - * @brief Synchronize entities with a MeshId component to an ACompMeshGl + * @brief Compile GPU-side MeshGlIds for meshes loaded from a Resource (MeshId + ResId) * + * @param rCtxDrawRes [in] Resources used by the scene + * @param rResources [ref] Application Resources shared with the scene. New resource owners may be created. + * @param rRenderGl [ref] Renderer state + */ + static void compile_resource_meshes( + ACtxDrawingRes const& rCtxDrawRes, + Resources& rResources, + RenderGL& rRenderGl); + + /** + * @brief Synchronize an entity's MeshId component to an ACompMeshGl + * + * @param ent [in] DrawEnt with mesh to synchronize * @param cmpMeshIds [in] Scene Mesh Id component * @param meshToRes [in] Scene's Mesh Id to Resource Id - * @param entsDirty [in] Entities to synchronize * @param rCmpMeshGl [ref] Renderer-side ACompMeshGl components * @param rRenderGl [ref] Renderer state */ - static void assign_meshes( - acomp_storage_t const& cmpMeshIds, - IdMap_t const& meshToRes, - std::vector const& entsDirty, - acomp_storage_t& rCmpMeshGl, - RenderGL& rRenderGl); + static void sync_drawent_mesh( + DrawEnt ent, + KeyedVec const& cmpMeshIds, + IdMap_t const& meshToRes, + MeshGlEntStorage_t& rCmpMeshGl, + RenderGL& rRenderGl); + + template + static void sync_drawent_mesh( + ITA_T const& first, + ITB_T const& last, + KeyedVec const& cmpMeshIds, + IdMap_t const& meshToRes, + MeshGlEntStorage_t& rCmpMeshGl, + RenderGL& rRenderGl) + { + std::for_each(first, last, [&] (DrawEnt const ent) + { + sync_drawent_mesh(ent, cmpMeshIds, meshToRes, rCmpMeshGl, rRenderGl); + }); + } /** * @brief Synchronize entities with a TexId component to an ACompTexGl @@ -173,12 +199,27 @@ class SysRenderGL * @param rCmpTexGl [ref] Renderer-side ACompTexGl components * @param rRenderGl [ref] Renderer state */ - static void assign_textures( - acomp_storage_t const& cmpTexIds, - IdMap_t const& texToRes, - std::vector const& entsDirty, - acomp_storage_t& rCmpTexGl, - RenderGL& rRenderGl); + static void sync_drawent_texture( + DrawEnt ent, + KeyedVec const& cmpTexIds, + IdMap_t const& texToRes, + TexGlEntStorage_t& rCmpTexGl, + RenderGL& rRenderGl); + + template + static void sync_drawent_texture( + ITA_T const& first, + ITB_T const& last, + KeyedVec const& cmpTexIds, + IdMap_t const& texToRes, + TexGlEntStorage_t& rCmpTexGl, + RenderGL& rRenderGl) + { + std::for_each(first, last, [&] (DrawEnt const ent) + { + sync_drawent_texture(ent, cmpTexIds, texToRes, rCmpTexGl, rRenderGl); + }); + } /** * @brief Call draw functions of a RenderGroup of opaque objects @@ -189,7 +230,7 @@ class SysRenderGL */ static void render_opaque( RenderGroup const& group, - acomp_storage_t const& visible, + DrawEntSet_t const& visible, ViewProjMatrix const& viewProj); /** @@ -203,28 +244,16 @@ class SysRenderGL */ static void render_transparent( RenderGroup const& group, - acomp_storage_t const& visible, + DrawEntSet_t const& visible, ViewProjMatrix const& viewProj); static void draw_group( RenderGroup const& group, - acomp_storage_t const& visible, + DrawEntSet_t const& visible, ViewProjMatrix const& viewProj); - template - static void update_delete( - ACtxSceneRenderGL &rCtxRenderGl, IT_T first, IT_T const& last); }; -template -void SysRenderGL::update_delete( - ACtxSceneRenderGL &rCtxRenderGl, IT_T first, IT_T const& last) -{ - rCtxRenderGl.m_meshId .remove(first, last); - rCtxRenderGl.m_diffuseTexId .remove(first, last); - rCtxRenderGl.m_drawTransform .remove(first, last); -} - } diff --git a/src/osp/Active/parts.h b/src/osp/Active/parts.h index ca4a0410..14b45393 100644 --- a/src/osp/Active/parts.h +++ b/src/osp/Active/parts.h @@ -50,7 +50,7 @@ struct Parts std::vector m_partToWeld; std::vector m_partTransformWeld; MapPartToMachines_t m_partToMachines; - std::vector m_partDirty; + std::vector m_partDirty; lgrn::IdRegistryStl m_weldIds; lgrn::IntArrayMultiMap m_weldToParts; diff --git a/src/osp/Active/physics.h b/src/osp/Active/physics.h index 0c96fd3c..deba606a 100644 --- a/src/osp/Active/physics.h +++ b/src/osp/Active/physics.h @@ -48,14 +48,14 @@ struct ACompMass struct ACtxPhysics { std::vector m_shape; - EntSet_t m_hasColliders; + ActiveEntSet_t m_hasColliders; acomp_storage_t m_mass; Vector3 m_originTranslate; - EntVector_t m_colliderDirty; + ActiveEntVec_t m_colliderDirty; std::vector< std::pair > m_setVelocity; diff --git a/src/osp/Shaders/Flat.cpp b/src/osp/Shaders/Flat.cpp index 933def2a..6c1a7c25 100644 --- a/src/osp/Shaders/Flat.cpp +++ b/src/osp/Shaders/Flat.cpp @@ -32,7 +32,7 @@ using namespace osp::active; using namespace osp::shader; void shader::draw_ent_flat( - ActiveEnt ent, ViewProjMatrix const& viewProj, + DrawEnt ent, ViewProjMatrix const& viewProj, EntityToDraw::UserData_t userData) noexcept { using Flag = Flat::Flag; @@ -46,22 +46,20 @@ void shader::draw_ent_flat( auto &rShader = *reinterpret_cast(pShader); // Collect uniform information - Matrix4 const &drawTf = rData.m_pDrawTf->get(ent); + Matrix4 const &drawTf = (*rData.m_pDrawTf)[ent]; if (rShader.flags() & Flag::Textured) { - TexGlId const texGlId = rData.m_pDiffuseTexId->get(ent).m_glId; + TexGlId const texGlId = (*rData.m_pDiffuseTexId)[ent].m_glId; rShader.bindTexture(rData.m_pTexGl->get(texGlId)); } if (rData.m_pColor != nullptr) { - rShader.setColor(rData.m_pColor->contains(ent) - ? rData.m_pColor->get(ent) - : 0xffffffff_rgbaf); + rShader.setColor((*rData.m_pColor)[ent]); } - MeshGlId const meshId = rData.m_pMeshId->get(ent).m_glId; + MeshGlId const meshId = (*rData.m_pMeshId)[ent].m_glId; Magnum::GL::Mesh &rMesh = rData.m_pMeshGl->get(meshId); rShader @@ -70,52 +68,3 @@ void shader::draw_ent_flat( } -void shader::assign_flat( - RenderGroup::ArrayView_t entities, - RenderGroup::Storage_t *pStorageOpaque, - RenderGroup::Storage_t *pStorageTransparent, - acomp_storage_t const& opaque, - acomp_storage_t const& diffuse, - ACtxDrawFlat &rData) -{ - for (ActiveEnt const ent : entities) - { - if (opaque.contains(ent)) - { - if (pStorageOpaque == nullptr) - { - continue; - } - - if (diffuse.contains(ent)) - { - pStorageOpaque->emplace( - ent, EntityToDraw{&draw_ent_flat, {&rData, &rData.m_shaderDiffuse} }); - } - else - { - pStorageOpaque->emplace( - ent, EntityToDraw{&draw_ent_flat, {&rData, &rData.m_shaderUntextured} }); - } - } - else - { - if (pStorageTransparent == nullptr) - { - continue; - } - - if (diffuse.contains(ent)) - { - pStorageTransparent->emplace( - ent, EntityToDraw{&draw_ent_flat, {&rData, &rData.m_shaderDiffuse} }); - } - else - { - pStorageTransparent->emplace( - ent, EntityToDraw{&draw_ent_flat, {&rData, &rData.m_shaderUntextured} }); - } - } - } -} - diff --git a/src/osp/Shaders/Flat.h b/src/osp/Shaders/Flat.h index a3779198..e3ab7f3f 100644 --- a/src/osp/Shaders/Flat.h +++ b/src/osp/Shaders/Flat.h @@ -43,10 +43,10 @@ struct ACtxDrawFlat Flat m_shaderUntextured {Corrade::NoCreate}; Flat m_shaderDiffuse {Corrade::NoCreate}; - acomp_storage_t *m_pDrawTf{nullptr}; - acomp_storage_t *m_pColor{nullptr}; - osp::active::ACompTexGlStorage_t *m_pDiffuseTexId{nullptr}; - osp::active::ACompMeshGlStorage_t *m_pMeshId{nullptr}; + osp::active::DrawTransforms_t *m_pDrawTf{nullptr}; + osp::KeyedVec *m_pColor{nullptr}; + osp::active::TexGlEntStorage_t *m_pDiffuseTexId{nullptr}; + osp::active::MeshGlEntStorage_t *m_pMeshId{nullptr}; osp::active::TexGlStorage_t *m_pTexGl{nullptr}; osp::active::MeshGlStorage_t *m_pMeshGl{nullptr}; @@ -65,7 +65,7 @@ struct ACtxDrawFlat }; void draw_ent_flat( - active::ActiveEnt ent, + active::DrawEnt ent, active::ViewProjMatrix const& viewProj, active::EntityToDraw::UserData_t userData) noexcept; @@ -73,20 +73,71 @@ void draw_ent_flat( * @brief Assign a Flat shader to a set of entities, and write results to * a RenderGroup * - * @param entities [in] Entities to consider + * @param dirtyFirst [in] Iterator to first entity to sync + * @param dirtyLast [in] Iterator to last entity to sync + * @param hasMaterial [in] Which entities a phong material is assigned to * @param pStorageOpaque [out] Optional RenderGroup storage for opaque * @param pStorageTransparent [out] Optional RenderGroup storage for transparent - * @param opaque [in] View for opaque component - * @param diffuse [in] View for diffuse texture component + * @param opaque [in] Storage for opaque component + * @param diffuse [in] Storage for diffuse texture component * @param rData [in] Phong shader data, stable memory required */ -void assign_flat( - active::RenderGroup::ArrayView_t entities, - active::RenderGroup::Storage_t *pStorageOpaque, - active::RenderGroup::Storage_t *pStorageTransparent, - active::acomp_storage_t const& opaque, - active::acomp_storage_t const& diffuse, - ACtxDrawFlat &rData); +template +void sync_flat( + ITA_T dirtyIt, + ITB_T const& dirtyLast, + active::DrawEntSet_t const& hasMaterial, + active::RenderGroup::Storage_t *const pStorageOpaque, + active::RenderGroup::Storage_t *const pStorageTransparent, + KeyedVec const& drawBasic, + active::TexGlEntStorage_t const& diffuse, + ACtxDrawFlat &rData) +{ + using namespace active; + + for (; dirtyIt != dirtyLast; std::advance(dirtyIt, 1)) + { + DrawEnt const ent = *dirtyIt; + + // Erase from group if they exist + if (pStorageOpaque != nullptr) + { + pStorageOpaque->remove(ent); + } + if (pStorageTransparent != nullptr) + { + pStorageTransparent->remove(ent); + } + + if ( ! hasMaterial.test(std::size_t(ent))) + { + continue; // Phong material is not assigned to this entity + } + + Flat *pShader = (diffuse[ent].m_glId != lgrn::id_null()) + ? &rData.m_shaderDiffuse + : &rData.m_shaderUntextured; + + if (drawBasic[ent].m_opaque) + { + if (pStorageOpaque == nullptr) + { + continue; + } + + pStorageOpaque->emplace(ent, EntityToDraw{&draw_ent_flat, {&rData, pShader} }); + } + else + { + if (pStorageTransparent == nullptr) + { + continue; + } + + pStorageTransparent->emplace(ent, EntityToDraw{&draw_ent_flat, {&rData, pShader} }); + } + } +} } // namespace osp::shader diff --git a/src/osp/Shaders/MeshVisualizer.cpp b/src/osp/Shaders/MeshVisualizer.cpp index 804f20e0..e10e0d31 100644 --- a/src/osp/Shaders/MeshVisualizer.cpp +++ b/src/osp/Shaders/MeshVisualizer.cpp @@ -36,7 +36,7 @@ using namespace osp::active; using namespace osp::shader; void shader::draw_ent_visualizer( - ActiveEnt ent, const ViewProjMatrix &viewProj, + DrawEnt ent, const ViewProjMatrix &viewProj, EntityToDraw::UserData_t userData) noexcept { using Magnum::Shaders::MeshVisualizerGL3D; @@ -45,7 +45,7 @@ void shader::draw_ent_visualizer( assert(pData != nullptr); auto &rData = *reinterpret_cast(pData); - Matrix4 const& drawTf = rData.m_pDrawTf->get(ent); + Matrix4 const& drawTf = (*rData.m_pDrawTf)[ent]; Matrix4 const entRelative = viewProj.m_view * drawTf; @@ -63,7 +63,7 @@ void shader::draw_ent_visualizer( Magnum::GL::Renderer::setDepthMask(false); } - MeshGlId const meshId = rData.m_pMeshId->get(ent).m_glId; + MeshGlId const meshId = (*rData.m_pMeshId)[ent].m_glId; Magnum::GL::Mesh &rMesh = rData.m_pMeshGl->get(meshId); rShader @@ -78,3 +78,30 @@ void shader::draw_ent_visualizer( } } +void shader::sync_drawent_visualizer( + active::DrawEnt const ent, + active::DrawEntSet_t const& hasMaterial, + active::RenderGroup::Storage_t& rStorage, + ACtxDrawMeshVisualizer &rData) +{ + using namespace active; + + bool alreadyAdded = rStorage.contains(ent); + if (hasMaterial.test(std::size_t(ent))) + { + if ( ! alreadyAdded) + { + rStorage.emplace( ent, EntityToDraw{&draw_ent_visualizer, {&rData} } ); + } + } + else + { + if (alreadyAdded) + { + rStorage.erase(ent); + } + } +} + + + diff --git a/src/osp/Shaders/MeshVisualizer.h b/src/osp/Shaders/MeshVisualizer.h index 590cadcd..f2fabbfb 100644 --- a/src/osp/Shaders/MeshVisualizer.h +++ b/src/osp/Shaders/MeshVisualizer.h @@ -37,10 +37,12 @@ struct ACtxDrawMeshVisualizer { MeshVisualizer m_shader{Corrade::NoCreate}; - active::acomp_storage_t *m_pDrawTf{nullptr}; - osp::active::ACompMeshGlStorage_t *m_pMeshId{nullptr}; + osp::active::DrawTransforms_t *m_pDrawTf{nullptr}; + osp::active::MeshGlEntStorage_t *m_pMeshId{nullptr}; osp::active::MeshGlStorage_t *m_pMeshGl{nullptr}; + osp::active::MaterialId m_materialId { lgrn::id_null() }; + bool m_wireframeOnly{false}; constexpr void assign_pointers(active::ACtxSceneRenderGL& rCtxScnGl, @@ -53,43 +55,28 @@ struct ACtxDrawMeshVisualizer }; void draw_ent_visualizer( - active::ActiveEnt ent, - active::ViewProjMatrix const& viewProj, - active::EntityToDraw::UserData_t userData) noexcept; + active::DrawEnt ent, + active::ViewProjMatrix const& viewProj, + active::EntityToDraw::UserData_t userData) noexcept; -template -void sync_visualizer( - ITA_T dirtyFirst, - ITB_T const& dirtyLast, - active::EntSet_t const& hasMaterial, - active::RenderGroup::Storage_t& rStorage, - ACtxDrawMeshVisualizer &rData) -{ - using namespace active; +void sync_drawent_visualizer( + active::DrawEnt const ent, + active::DrawEntSet_t const& hasMaterial, + active::RenderGroup::Storage_t& rStorage, + ACtxDrawMeshVisualizer& rData); - while (dirtyFirst != dirtyLast) +template +static void sync_drawent_visualizer( + ITA_T const& first, + ITB_T const& last, + active::DrawEntSet_t const& hasMaterial, + active::RenderGroup::Storage_t& rStorage, + ACtxDrawMeshVisualizer& rData) +{ + std::for_each(first, last, [&] (active::DrawEnt const ent) { - ActiveEnt const ent = *dirtyFirst; - bool alreadyAdded = rStorage.contains(ent); - if (hasMaterial.test(std::size_t(ent))) - { - if ( ! alreadyAdded) - { - rStorage.emplace( ent, EntityToDraw{&draw_ent_visualizer, {&rData} } ); - } - } - else - { - if (alreadyAdded) - { - rStorage.erase(ent); - } - } - - std::advance(dirtyFirst, 1); - } - - + sync_drawent_visualizer(ent, hasMaterial, rStorage, rData); + }); } } // namespace osp::shader diff --git a/src/osp/Shaders/Phong.cpp b/src/osp/Shaders/Phong.cpp index 208d2d88..49316e09 100644 --- a/src/osp/Shaders/Phong.cpp +++ b/src/osp/Shaders/Phong.cpp @@ -32,7 +32,7 @@ using namespace osp::active; using namespace osp::shader; void shader::draw_ent_phong( - ActiveEnt ent, ViewProjMatrix const& viewProj, + DrawEnt ent, ViewProjMatrix const& viewProj, EntityToDraw::UserData_t userData) noexcept { using Flag = Phong::Flag; @@ -46,7 +46,7 @@ void shader::draw_ent_phong( auto &rShader = *reinterpret_cast(pShader); // Collect uniform information - Matrix4 const &drawTf = rData.m_pDrawTf->get(ent); + Matrix4 const &drawTf = (*rData.m_pDrawTf)[ent]; Magnum::Matrix4 entRelative = viewProj.m_view * drawTf; @@ -58,7 +58,7 @@ void shader::draw_ent_phong( if (rShader.flags() & Flag::DiffuseTexture) { - TexGlId const texGlId = rData.m_pDiffuseTexId->get(ent).m_glId; + TexGlId const texGlId = (*rData.m_pDiffuseTexId)[ent].m_glId; Magnum::GL::Texture2D &rTexture = rData.m_pTexGl->get(texGlId); rShader.bindDiffuseTexture(rTexture); @@ -70,23 +70,45 @@ void shader::draw_ent_phong( if (rData.m_pColor != nullptr) { - rShader.setDiffuseColor(rData.m_pColor->contains(ent) - ? rData.m_pColor->get(ent) - : 0xffffffff_rgbaf); + rShader.setDiffuseColor((*rData.m_pColor)[ent]); } - MeshGlId const meshId = rData.m_pMeshId->get(ent).m_glId; + MeshGlId const meshId = (*rData.m_pMeshId)[ent].m_glId; Magnum::GL::Mesh &rMesh = rData.m_pMeshGl->get(meshId); + Matrix3 a{viewProj.m_view}; + + + // Lights with w=0.0f are directional lights + // Directonal lights are camera-relative, so we need 'viewProj.m_view *' + auto const lightPositions = + { + viewProj.m_view * Vector4{ Vector3{0.2f, 0.6f, 0.5f}.normalized(), 0.0f}, + viewProj.m_view * Vector4{-Vector3{0.0f, 0.0f, 1.0f}, 0.0f} + }; + + auto const lightColors = + { + 0xddd4Cd_rgbf, + 0x32354e_rgbf + }; + + auto const lightSpecColors = + { + 0xfff5ed_rgbf, + 0x000000_rgbf + }; + + // TODO: find a better way to deal with lights instead of hard-coding it rShader - .setAmbientColor(0x000000ff_rgbaf) + .setAmbientColor(0x1a1e29ff_rgbaf) .setSpecularColor(0xffffff00_rgbaf) - .setLightColors({0xfff5ec_rgbf, 0xe4e8ff_rgbf}) - .setLightPositions({ Vector4{ Vector3{0.2f, 0.6f, 0.5f}.normalized(), 0.0f}, - Vector4{-Vector3{0.2f, 0.6f, 0.5f}.normalized(), 0.0f} }) + .setLightColors(lightColors) + .setLightSpecularColors(lightSpecColors) + .setLightPositions(lightPositions) .setTransformationMatrix(entRelative) .setProjectionMatrix(viewProj.m_proj) - .setNormalMatrix(Matrix3{drawTf}) + .setNormalMatrix(entRelative.normalMatrix()) .draw(rMesh); } diff --git a/src/osp/Shaders/Phong.h b/src/osp/Shaders/Phong.h index 7db3f946..50ea190e 100644 --- a/src/osp/Shaders/Phong.h +++ b/src/osp/Shaders/Phong.h @@ -44,10 +44,10 @@ struct ACtxDrawPhong Phong m_shaderUntextured {Corrade::NoCreate}; Phong m_shaderDiffuse {Corrade::NoCreate}; - acomp_storage_t *m_pDrawTf{nullptr}; - acomp_storage_t *m_pColor{nullptr}; - osp::active::ACompTexGlStorage_t *m_pDiffuseTexId{nullptr}; - osp::active::ACompMeshGlStorage_t *m_pMeshId{nullptr}; + osp::active::DrawTransforms_t *m_pDrawTf{nullptr}; + osp::KeyedVec *m_pColor{nullptr}; + osp::active::TexGlEntStorage_t *m_pDiffuseTexId{nullptr}; + osp::active::MeshGlEntStorage_t *m_pMeshId{nullptr}; osp::active::TexGlStorage_t *m_pTexGl{nullptr}; osp::active::MeshGlStorage_t *m_pMeshGl{nullptr}; @@ -66,7 +66,7 @@ struct ACtxDrawPhong }; void draw_ent_phong( - active::ActiveEnt ent, + active::DrawEnt ent, active::ViewProjMatrix const& viewProj, active::EntityToDraw::UserData_t userData) noexcept; @@ -85,20 +85,20 @@ void draw_ent_phong( */ template void sync_phong( - ITA_T dirtyFirst, + ITA_T dirtyIt, ITB_T const& dirtyLast, - active::EntSet_t const& hasMaterial, + active::DrawEntSet_t const& hasMaterial, active::RenderGroup::Storage_t *const pStorageOpaque, active::RenderGroup::Storage_t *const pStorageTransparent, - active::acomp_storage_t const& opaque, - active::acomp_storage_t const& diffuse, + KeyedVec const& drawBasic, + active::TexGlEntStorage_t const& diffuse, ACtxDrawPhong &rData) { using namespace active; - while (dirtyFirst != dirtyLast) + for (; dirtyIt != dirtyLast; std::advance(dirtyIt, 1)) { - ActiveEnt const ent = *dirtyFirst; + DrawEnt const ent = *dirtyIt; // Erase from group if they exist if (pStorageOpaque != nullptr) @@ -115,9 +115,11 @@ void sync_phong( continue; // Phong material is not assigned to this entity } - Phong *pShader = diffuse.contains(ent) ? &rData.m_shaderDiffuse : &rData.m_shaderUntextured; + Phong *pShader = (diffuse[ent].m_glId != lgrn::id_null()) + ? &rData.m_shaderDiffuse + : &rData.m_shaderUntextured; - if (opaque.contains(ent)) + if (drawBasic[ent].m_opaque) { if (pStorageOpaque == nullptr) { @@ -135,8 +137,6 @@ void sync_phong( pStorageTransparent->emplace(ent, EntityToDraw{&draw_ent_phong, {&rData, pShader} }); } - - std::advance(dirtyFirst, 1); } } diff --git a/src/osp/UserInputHandler.h b/src/osp/UserInputHandler.h index 08269274..f1663d78 100644 --- a/src/osp/UserInputHandler.h +++ b/src/osp/UserInputHandler.h @@ -359,6 +359,9 @@ class ControlSubscriber : m_pInputHandler(pInputHandler) { } + ControlSubscriber(ControlSubscriber&& move) noexcept = default; + ControlSubscriber& operator=(ControlSubscriber&& move) noexcept = default; + ~ControlSubscriber(); EButtonControlIndex button_subscribe(std::string_view name); diff --git a/src/osp/bitvector.h b/src/osp/bitvector.h index 79855fb4..81fb33d2 100644 --- a/src/osp/bitvector.h +++ b/src/osp/bitvector.h @@ -32,7 +32,8 @@ namespace osp { -using BitVector_t = lgrn::BitView< std::vector >; +using bitint_t = uint64_t; +using BitVector_t = lgrn::BitView< std::vector >; inline void bitvector_resize(BitVector_t &rBitVector, std::size_t size) { diff --git a/src/osp/keyed_vector.h b/src/osp/keyed_vector.h new file mode 100644 index 00000000..22230919 --- /dev/null +++ b/src/osp/keyed_vector.h @@ -0,0 +1,82 @@ +/** + * Open Space Program + * Copyright © 2019-2023 Open Space Program Project + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#pragma once + +#include + +#include + +namespace osp +{ + +/** + * @brief Wraps an std::vector intended to be accessed using a (strong typedef) enum class ID + */ +template > +class KeyedVec : public std::vector +{ + using vector_t = std::vector; + using int_t = lgrn::underlying_int_type_t; + +public: + + using value_type = typename vector_t::value_type; + using allocator_type = typename vector_t::allocator_type; + using reference = typename vector_t::reference; + using const_reference = typename vector_t::const_reference; + using pointer = typename vector_t::pointer; + using const_pointer = typename vector_t::const_pointer; + using iterator = typename vector_t::iterator; + using const_iterator = typename vector_t::const_iterator; + using reverse_iterator = typename vector_t::reverse_iterator; + using const_reverse_iterator = typename vector_t::const_reverse_iterator; + using difference_type = typename vector_t::difference_type; + using size_type = typename vector_t::size_type; + + reference at(ID_T const id) + { + return vector_t::at(std::size_t(id)); + } + + const_reference at(ID_T const id) const + { + return vector_t::at(std::size_t(id)); + } + + reference operator[] (ID_T const id) + { + return vector_t::operator[](std::size_t(id)); + } + + const_reference operator[] (ID_T const id) const + { + return vector_t::operator[](std::size_t(id)); + } + +}; // class KeyedVec + + + +} // namespace osp diff --git a/src/osp/link/machines.cpp b/src/osp/link/machines.cpp index b25592e8..5524b20c 100644 --- a/src/osp/link/machines.cpp +++ b/src/osp/link/machines.cpp @@ -27,9 +27,6 @@ namespace osp::link { -NodeTypeId const gc_ntSigFloat = NodeTypeReg_t::create(); - - void copy_nodes( Nodes const &rSrcNodes, Machines const &rSrcMach, diff --git a/src/osp/link/machines.h b/src/osp/link/machines.h index 4795c83a..629208a8 100644 --- a/src/osp/link/machines.h +++ b/src/osp/link/machines.h @@ -51,7 +51,7 @@ using JuncCustom = uint16_t; using MachTypeReg_t = GlobalIdReg; using NodeTypeReg_t = GlobalIdReg; -extern NodeTypeId const gc_ntSigFloat; +inline NodeTypeId const gc_ntSigFloat = NodeTypeReg_t::create(); /** * @brief Keeps track of Machines of a certain type that exists diff --git a/src/osp/tasks/builder.h b/src/osp/tasks/builder.h index 91d00de1..3bd2a794 100644 --- a/src/osp/tasks/builder.h +++ b/src/osp/tasks/builder.h @@ -25,12 +25,12 @@ #pragma once #include "tasks.h" +#include "../types.h" +#include #include -#include -#include - +#include #include #include #include @@ -38,178 +38,274 @@ namespace osp { -using Corrade::Containers::ArrayView; - -inline void set_tags( - Corrade::Containers::ArrayView tagsIn, - ArrayView taggedInts, - std::size_t const taggedSize, - std::size_t const taggedId) noexcept +/** + * @brief A convenient interface for setting up Tasks and required task data + */ +template +struct TaskBuilderBase { - std::size_t const offset = taggedId * taggedSize; - auto taggedBits = lgrn::BitView(taggedInts.slice(offset, offset + taggedSize)); + using Builder_t = typename TRAITS_T::Builder_t; + using TaskRef_t = typename TRAITS_T::TaskRef_t; + + template + using PipelineRef_t = typename TRAITS_T::template PipelineRef_t; + + constexpr TaskBuilderBase(Tasks &rTasks, TaskEdges &rEdges) noexcept + : m_rTasks{rTasks} + , m_rEdges{rEdges} + { } - for (uint32_t const tag : tagsIn) + TaskRef_t task() { - taggedBits.set(tag); - } -} + TaskId const taskId = m_rTasks.m_taskIds.create(); -/** - * @brief A convenient interface for setting up TaskTags and required task data - */ -template -class TaskBuilder -{ - using Tags_t = Corrade::Containers::ArrayView; + std::size_t const capacity = m_rTasks.m_taskIds.capacity(); -public: + return task(taskId); + }; - struct TaskRef + [[nodiscard]] constexpr TaskRef_t task(TaskId const taskId) noexcept { - constexpr operator TaskId() noexcept - { - return m_taskId; - } + return TaskRef_t{ + taskId, + static_cast(*this) + }; + } - TaskRef& assign(Tags_t const tags) noexcept - { - set_tags(Corrade::Containers::arrayCast(tags), - m_rTasks.m_taskTags, - m_rTags.tag_ints_per_task(), - std::size_t(m_taskId)); - return *this; - } + template + [[nodiscard]] constexpr PipelineRef_t pipeline(PipelineDef pipelineDef) noexcept + { + return PipelineRef_t{ + pipelineDef.m_value, + static_cast(*this) + }; + } - TaskRef& assign(std::initializer_list tags) noexcept - { - return assign(Corrade::Containers::arrayView(tags)); - } + template + TGT_STRUCT_T create_pipelines(osp::ArrayView const pipelinesOut) + { + static_assert(sizeof(TGT_STRUCT_T) % sizeof(PipelineDefBlank_t) == 0); + constexpr std::size_t count = sizeof(TGT_STRUCT_T) / sizeof(PipelineDefBlank_t); + + LGRN_ASSERTMV(count == pipelinesOut.size() , + "The number of members in TGT_STRUCT_T must match the number of output pipelines", + count, pipelinesOut.size()); + + m_rTasks.m_pipelineIds.create(pipelinesOut.begin(), pipelinesOut.end()); + + std::size_t const capacity = m_rTasks.m_pipelineIds.capacity(); + + m_rTasks.m_pipelineInfo .resize(capacity); + m_rTasks.m_pipelineControl.resize(capacity); + m_rTasks.m_pipelineParents.resize(capacity, lgrn::id_null()); + + // Set m_value members of TGT_STRUCT_T, asserted to contain only PipelineDef<...> + // This is janky enough that rewriting the code below might cause it to ONLY SEGFAULT ON + // RELEASE AND ISN'T CAUGHT BY ASAN WTF??? (on gcc 11) - template - TaskRef& data(ARGS_T&& ... args) noexcept + alignas(TGT_STRUCT_T) std::array bytes; + TGT_STRUCT_T *pOut = new(bytes.data()) TGT_STRUCT_T; + + for (std::size_t i = 0; i < count; ++i) { - task_data(m_rData, m_taskId, std::forward(args) ...); - return *this; + PipelineId const pl = pipelinesOut[i]; + unsigned char *pDefBytes = bytes.data() + sizeof(PipelineDefBlank_t)*i; + + *reinterpret_cast(pDefBytes + offsetof(PipelineDefBlank_t, m_value)) = pl; + + m_rTasks.m_pipelineInfo[pl].stageType = *reinterpret_cast(pDefBytes + offsetof(PipelineDefBlank_t, m_type)); + m_rTasks.m_pipelineInfo[pl].name = *reinterpret_cast(pDefBytes + offsetof(PipelineDefBlank_t, m_name)); } - TaskId const m_taskId; - Tags & m_rTags; - Tasks & m_rTasks; - DATA_T & m_rData; + return *pOut; + } + + template + [[nodiscard]] TGT_STRUCT_T create_pipelines() + { + static_assert(sizeof(TGT_STRUCT_T) % sizeof(PipelineDefBlank_t) == 0); + constexpr std::size_t count = sizeof(TGT_STRUCT_T) / sizeof(PipelineDefBlank_t); + + std::array pipelines; - }; // struct TaskRefSpec + return create_pipelines(osp::ArrayView{pipelines.data(), count}); + } - struct TagRef + template + std::array create_pipelines() { + std::array out; + m_rTasks.m_pipelineIds.create(out.begin(), out.end()); + return out; + } - TagRef& depend_on(Tags_t tags) noexcept - { - auto depends = osp::tag_depends_2d(m_rTags)[std::size_t(m_tagId)].asContiguous(); - auto const depBegin = std::begin(depends); - auto const depEnd = std::end(depends); - - for (TagId const tag : tags) - { - // Find first empty spot or already-added dependency - auto dependIt = std::find_if( - depBegin, depEnd, [tag] (TagId const lhs) - { - return (lhs == lgrn::id_null()) || (lhs == tag); - }); - - // No space left for any dependencies - assert(dependIt != depEnd); - - *dependIt = tag; - } - - return *this; - } + Tasks & m_rTasks; + TaskEdges & m_rEdges; - TagRef& depend_on(std::initializer_list tags) noexcept - { - return depend_on(Corrade::Containers::arrayView(tags)); - } +}; // class TaskBuilderBase - TagRef& limit(unsigned int lim) - { - m_rTags.m_tagLimits[std::size_t(m_tagId)] = lim; - return *this; - } - TagRef& set_external(bool value) +template +struct TaskRefBase +{ + using Builder_t = typename TRAITS_T::Builder_t; + using TaskRef_t = typename TRAITS_T::TaskRef_t; + + constexpr operator TaskId() noexcept + { + return m_taskId; + } + + constexpr Tasks & tasks() noexcept { return m_rBuilder.m_rTasks; } + + template + TaskRef_t& add_edges(std::vector& rContainer, RANGE_T const& add) + { + for (auto const [pipeline, stage] : add) { - auto bitView = lgrn::bit_view(m_rTags.m_tagExtern); - if (value) - { - bitView.set(std::size_t(m_tagId)); - } - else - { - bitView.reset(std::size_t(m_tagId)); - } - return *this; + rContainer.push_back({ + .task = m_taskId, + .pipeline = pipeline, + .stage = stage + }); } + return static_cast(*this); + } - TagId const m_tagId; - Tags & m_rTags; + TaskRef_t& run_on(TplPipelineStage const tpl) noexcept + { + m_rBuilder.m_rTasks.m_taskRunOn.resize(m_rBuilder.m_rTasks.m_taskIds.capacity()); + m_rBuilder.m_rTasks.m_taskRunOn[m_taskId] = tpl; - }; // struct TagRef + return static_cast(*this); + } - constexpr TaskBuilder(Tags& rTags, Tasks& rTasks, DATA_T& rData) - : m_rTags{rTags} - , m_rTasks{rTasks} - , m_rData{rData} - { } + TaskRef_t& schedules(TplPipelineStage const tpl) noexcept + { + m_rBuilder.m_rTasks.m_pipelineControl[tpl.pipeline].scheduler = m_taskId; - template - std::array create_tags() + return run_on(tpl); + } + + TaskRef_t& sync_with(ArrayView const specs) noexcept { - std::array out; + return add_edges(m_rBuilder.m_rEdges.m_syncWith, specs); + } - [[maybe_unused]] auto const it - = m_rTags.m_tags.create(std::begin(out), std::end(out)); + TaskRef_t& sync_with(std::initializer_list specs) noexcept + { + return add_edges(m_rBuilder.m_rEdges.m_syncWith, specs); + } - assert(it == std::end(out)); // Auto-resizing tags not (yet?) supported + TaskId m_taskId; + Builder_t & m_rBuilder; - return out; +}; // struct TaskRefBase + +template +struct PipelineRefBase +{ + using Builder_t = typename TRAITS_T::Builder_t; + using PipelineRef_t = typename TRAITS_T::template PipelineRef_t; + using TaskRef_t = typename TRAITS_T::TaskRef_t; + + constexpr operator PipelineId() noexcept + { + return m_pipelineId; } - TagRef tag(TagId const tagId) + PipelineRef_t& parent(PipelineId const parent) { - return { - .m_tagId = tagId, - .m_rTags = m_rTags - }; + m_rBuilder.m_rTasks.m_pipelineParents[m_pipelineId] = parent; + return static_cast(*this); } - TaskRef task() + PipelineRef_t& parent_with_schedule(PipelineId const parent) { - TaskId const taskId = m_rTasks.m_tasks.create(); + m_rBuilder.m_rTasks.m_pipelineParents[m_pipelineId] = parent; - std::size_t const capacity = m_rTasks.m_tasks.capacity(); + constexpr ENUM_T const scheduleStage = stage_schedule(ENUM_T{0}); + static_assert(scheduleStage != lgrn::id_null(), "Pipeline type has no schedule stage"); - m_rTasks.m_taskTags.resize(capacity * m_rTags.tag_ints_per_task()); + TaskId const scheduler = m_rBuilder.m_rTasks.m_pipelineControl[parent].scheduler; + LGRN_ASSERTM(scheduler != lgrn::id_null(), "Parent Pipeline has no scheduler task"); - return task(taskId); - }; + m_rBuilder.m_rEdges.m_syncWith.push_back({ + .task = scheduler, + .pipeline = m_pipelineId, + .stage = StageId(scheduleStage) + }); + + return static_cast(*this); + } - constexpr TaskRef task(TaskId const taskId) noexcept + PipelineRef_t& loops(bool const loop) { - return { - .m_taskId = taskId, - .m_rTags = m_rTags, - .m_rTasks = m_rTasks, - .m_rData = m_rData - }; + m_rBuilder.m_rTasks.m_pipelineControl[m_pipelineId].isLoopScope = loop; + return static_cast(*this); + } + + PipelineRef_t& wait_for_signal(ENUM_T stage) + { + m_rBuilder.m_rTasks.m_pipelineControl[m_pipelineId].waitStage = StageId(stage); + return static_cast(*this); } -private: - Tags & m_rTags; - Tasks & m_rTasks; - DATA_T & m_rData; + PipelineId m_pipelineId; + Builder_t & m_rBuilder; + +}; // struct TaskRefBase + +template +struct BasicBuilderTraits +{ + using Func_t = FUNC_T; + using FuncVec_t = KeyedVec; + + struct Builder; + struct TaskRef; + + template + struct PipelineRef; + + using Builder_t = Builder; + + using TaskRef_t = TaskRef; + + template + using PipelineRef_t = PipelineRef; -}; // class TaskBuilder + template + struct PipelineRef : public PipelineRefBase + { }; + + struct Builder : public TaskBuilderBase + { + Builder(Tasks& rTasks, TaskEdges& rEdges, FuncVec_t& rFuncs) + : TaskBuilderBase{ rTasks, rEdges } + , m_rFuncs{rFuncs} + { } + Builder(Builder const& copy) = delete; + Builder(Builder && move) = default; + + Builder& operator=(Builder const& copy) = delete; + + FuncVec_t & m_rFuncs; + }; + + struct TaskRef : public TaskRefBase + { + TaskRef& func(FUNC_T && in); + }; + +}; // struct BasicBuilderTraits + +template +typename BasicBuilderTraits::TaskRef& BasicBuilderTraits::TaskRef::func(FUNC_T && in) +{ + this->m_rBuilder.m_rFuncs.resize(this->m_rBuilder.m_rTasks.m_taskIds.capacity()); + this->m_rBuilder.m_rFuncs[this->m_taskId] = std::move(in); + return *this; +} } // namespace osp diff --git a/src/osp/tasks/execute.cpp b/src/osp/tasks/execute.cpp new file mode 100644 index 00000000..a2d24a2b --- /dev/null +++ b/src/osp/tasks/execute.cpp @@ -0,0 +1,920 @@ +/** + * Open Space Program + * Copyright © 2019-2022 Open Space Program Project + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "execute.h" + +#include + +namespace osp +{ + +static void exec_log(ExecContext &rExec, ExecContext::LogMsg_t msg) noexcept; + +static void pipeline_run_root(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, PipelineId pipeline) noexcept; + +static int pipeline_run(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, bool rerunLoop, PipelineId pipeline, PipelineTreePos_t treePos, uint32_t descendents, bool isLoopScope, bool insideLoopScope); + +static void pipeline_advance_stage(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, PipelineId pipeline) noexcept; + +static void pipeline_advance_reqs(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, PipelineId pipeline) noexcept; + +static void pipeline_advance_run(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, PipelineId pipeline) noexcept; + +static constexpr bool pipeline_can_advance(ExecPipeline &rExecPl) noexcept; + +static void pipeline_try_advance(ExecContext &rExec, ExecPipeline &rExecPl, PipelineId pipeline) noexcept; + +static void pipeline_cancel(Tasks const& tasks, TaskGraph const& graph, ExecContext& rExec, ExecPipeline& rExecPl, PipelineId pipeline) noexcept; + +struct ArgsForIsPipelineInLoop +{ + PipelineId viewedFrom; + PipelineId insideLoop; +}; + +static bool is_pipeline_in_loop(Tasks const& tasks, TaskGraph const& graph, ExecContext const& exec, ArgsForIsPipelineInLoop args) noexcept; + +struct ArgsForSubtreeForEach +{ + PipelineTreePos_t root; + bool includeRoot; +}; + +template +static void subtree_for_each(ArgsForSubtreeForEach args, TaskGraph const& graph, ExecContext const& rExec, FUNC_T&& func); + +//----------------------------------------------------------------------------- + +// Main entry-point functions + +void exec_update(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec) noexcept +{ + exec_log(rExec, ExecContext::UpdateStart{}); + + if (rExec.hasRequestRun) + { + for (ExecPipeline const& rExecPl : rExec.plData) + { + LGRN_ASSERTM( ! rExecPl.running, "Running new pipelines while already running is not yet supported ROFL! " + "Make sure all pipelines are finished running."); + } + + for (PipelineInt const plInt : rExec.plRequestRun.ones()) + { + pipeline_run_root(tasks, graph, rExec, PipelineId(plInt)); + } + rExec.plRequestRun.reset(); + rExec.hasRequestRun = false; + } + + + while (rExec.hasPlAdvanceOrLoop) + { + exec_log(rExec, ExecContext::UpdateCycle{}); + + rExec.hasPlAdvanceOrLoop = false; + + for (auto const [pipeline, treePos] : rExec.requestLoop) + { + ExecPipeline &rExecPl = rExec.plData[pipeline]; + LGRN_ASSERT(rExecPl.loopChildrenLeft == 0); + } + + rExec.requestLoop.clear(); + + for (PipelineInt const plInt : rExec.plAdvance.ones()) + { + pipeline_advance_stage(tasks, graph, rExec, PipelineId(plInt)); + } + + for (PipelineInt const plInt : rExec.plAdvance.ones()) + { + pipeline_advance_reqs(tasks, graph, rExec, PipelineId(plInt)); + } + + for (PipelineInt const plInt : rExec.plAdvance.ones()) + { + pipeline_advance_run(tasks, graph, rExec, PipelineId(plInt)); + } + + std::copy(rExec.plAdvanceNext.ints().begin(), + rExec.plAdvanceNext.ints().end(), + rExec.plAdvance .ints().begin()); + rExec.plAdvanceNext.reset(); + } + + exec_log(rExec, ExecContext::UpdateEnd{}); +} + +void complete_task(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, TaskId const task, TaskActions actions) noexcept +{ + LGRN_ASSERT(rExec.tasksQueuedRun.contains(task)); + rExec.tasksQueuedRun.erase(task); + + exec_log(rExec, ExecContext::CompleteTask{task}); + + auto const [pipeline, stage] = tasks.m_taskRunOn[task]; + ExecPipeline &rExecPl = rExec.plData[pipeline]; + + -- rExecPl.tasksQueuedRun; + + pipeline_try_advance(rExec, rExecPl, pipeline); + + if (actions & TaskAction::Cancel) + { + LGRN_ASSERTMV(rExecPl.tasksQueuedRun == 0 && rExecPl.tasksQueuedBlocked == 0, + "Tasks that cancel the pipeline should be the only task running. Too lazy to enforce this elsewhere LOL", + rExecPl.tasksQueuedRun, rExecPl.tasksQueuedBlocked); + pipeline_cancel(tasks, graph, rExec, rExecPl, pipeline); + } + + // Handle stages requiring this task + for (AnyStageId const& reqTaskAnystg : fanout_view(graph.taskToFirstRevStgreqtask, graph.revStgreqtaskToStage, task)) + { + PipelineId const reqPl = graph.anystgToPipeline[reqTaskAnystg]; + StageId const reqStg = stage_from(graph, reqPl, reqTaskAnystg); + ExecPipeline &rReqExecPl = rExec.plData[reqPl]; + + if (rReqExecPl.stage == reqStg) + { + // True if 'reqPl' sees that 'pipeline' is inside a loop, and may run more times + bool const loopsRelative = rExecPl.loop && is_pipeline_in_loop(tasks, graph, rExec, {.viewedFrom = reqPl, .insideLoop = pipeline}); + + if ( ( ! loopsRelative ) || rExecPl.canceled ) + { + LGRN_ASSERT(rReqExecPl.ownStageReqTasksLeft != 0); + -- rReqExecPl.ownStageReqTasksLeft; + pipeline_try_advance(rExec, rReqExecPl, reqPl); + } + } + else + { + LGRN_ASSERTMV(int(rReqExecPl.stage) < int(reqStg) && rReqExecPl.stage != lgrn::id_null(), + "Stage-requires-Task means that rReqExecPl.stage cannot advance any further than reqStg until task completes.", + int(task), int(rReqExecPl.stage), int(reqStg)); + } + } + + // Handle this task requiring stages from other pipelines + for (TaskRequiresStage const& req : fanout_view(graph.taskToFirstTaskreqstg, graph.taskreqstgData, task)) + { + ExecPipeline &rReqExecPl = rExec.plData[req.reqPipeline]; + + LGRN_ASSERTMV(rReqExecPl.stage == req.reqStage, + "Task-requires-Stage means this task should have not run unless the stage is selected", + int(task), int(rReqExecPl.stage), int(req.reqStage)); + + bool const loopsRelative = rExecPl.loop && is_pipeline_in_loop(tasks, graph, rExec, {.viewedFrom = req.reqPipeline, .insideLoop = pipeline}); + + if ( ( ! loopsRelative ) || rExecPl.canceled ) + { + LGRN_ASSERT(rReqExecPl.tasksReqOwnStageLeft != 0); + -- rReqExecPl.tasksReqOwnStageLeft; + pipeline_try_advance(rExec, rReqExecPl, req.reqPipeline); + } + // else: This pipeline is enclosed in a loop relative to dependency. This task may run + // more times. + } +} + +void exec_signal(ExecContext &rExec, PipelineId pipeline) noexcept +{ + ExecPipeline &rExecPl = rExec.plData[pipeline]; + exec_log(rExec, ExecLog::ExternalSignal{pipeline, ! rExecPl.waitSignaled}); + + if ( ! rExecPl.waitSignaled ) + { + rExecPl.waitSignaled = true; + pipeline_try_advance(rExec, rExecPl, pipeline); + } +} + +//----------------------------------------------------------------------------- + +// Major steps + +static void pipeline_run_root(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, PipelineId const pipeline) noexcept +{ + exec_log(rExec, ExecLog::ExternalRunRequest{pipeline}); + + PipelineTreePos_t const treePos = graph.pipelineToPltree[pipeline]; + + bool const isLoopScope = tasks.m_pipelineControl[pipeline].isLoopScope; + + uint32_t const descendents = (treePos != lgrn::id_null()) + ? graph.pltreeDescendantCounts[treePos] + : 0; + + pipeline_run(tasks, graph, rExec, false, pipeline, treePos, descendents, isLoopScope, false); +} + +static int pipeline_run(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, bool const rerunLoop, PipelineId const pipeline, PipelineTreePos_t const treePos, uint32_t const descendents, bool const isLoopScope, bool const insideLoopScope) +{ + auto const stageCount = fanout_size(graph.pipelineToFirstAnystg, pipeline); + ExecPipeline &rExecPl = rExec.plData[pipeline]; + + if (rExecPl.stage != lgrn::id_null()) + { + return 0; + } + + if (stageCount != 0) + { + if ( ! rExecPl.running ) + { + rExecPl.running = true; + rExecPl.loop = isLoopScope || insideLoopScope; + + ++ rExec.pipelinesRunning; + + rExec.plAdvance.set(std::size_t(pipeline)); + exec_log(rExec, ExecContext::PipelineRun{pipeline}); + } + + if (rerunLoop) + { + rExecPl.canceled = false; + + rExec.plAdvanceNext.set(std::size_t(pipeline)); + exec_log(rExec, ExecContext::PipelineLoop{pipeline}); + } + + rExec.hasPlAdvanceOrLoop = true; + } + + if (descendents == 0) + { + return 0; + } + + PipelineTreePos_t const childPosLast = treePos + 1 + descendents; + PipelineTreePos_t childPos = treePos + 1; + + int scopeChildCount = 0; + + while (childPos != childPosLast) + { + uint32_t const childDescendents = graph.pltreeDescendantCounts[childPos]; + PipelineId const childPl = graph.pltreeToPipeline[childPos]; + bool const childIsLoopScope = tasks.m_pipelineControl[childPl].isLoopScope; + + int const childChildCount = pipeline_run(tasks, graph, rExec, rerunLoop, childPl, childPos, childDescendents, childIsLoopScope, isLoopScope || insideLoopScope); + + scopeChildCount += childChildCount + 1; // + 1 for child itself + + childPos += 1 + childDescendents; + } + + if (isLoopScope) + { + rExecPl.loopChildrenLeft = scopeChildCount; + + return 0; + } + else + { + return scopeChildCount; + } +} + + +static void loop_scope_done(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, ExecPipeline &rExecPl, PipelineId const pipeline, PipelineTreePos_t const treePos) +{ + if ( ! rExecPl.canceled ) + { + // Loop more + + uint32_t const descendents = graph.pltreeDescendantCounts[treePos]; + pipeline_run(tasks, graph, rExec, true, pipeline, treePos, descendents, true, /* dont-care */ false); + } + else + { + // Loop finished + + LGRN_ASSERT(rExecPl.loopChildrenLeft == 0); + + subtree_for_each( + {.root = treePos, .includeRoot = true}, graph, rExec, + [&tasks, &graph, &rExec] + (PipelineTreePos_t const loopPos, PipelineId const loopPipeline, uint32_t const descendants) + { + ExecPipeline &rLoopExecPl = rExec.plData[loopPipeline]; + + LGRN_ASSERT(rLoopExecPl.running == true); + LGRN_ASSERT(rLoopExecPl.stage == lgrn::id_null()); + + rLoopExecPl.running = false; + rLoopExecPl.canceled = false; + rLoopExecPl.loop = false; + + LGRN_ASSERT(rExec.pipelinesRunning != 0); + -- rExec.pipelinesRunning; + + exec_log(rExec, ExecContext::PipelineLoopFinish{loopPipeline}); + }); + + // If this is a nested loop, decrement parent loop scope's loopChildrenLeft + + PipelineId const parentScopePl = tasks.m_pipelineParents[pipeline]; + if (parentScopePl == lgrn::id_null()) + { + return; // Loop is in the root + } + + PipelineTreePos_t const parentScopeTreePos = graph.pipelineToLoopScope[parentScopePl]; + if (parentScopeTreePos == lgrn::id_null()) + { + return; // Parent does not loop + } + + ExecPipeline &rParentScopeExecPl = rExec.plData[parentScopePl]; + + LGRN_ASSERT(rParentScopeExecPl.loopChildrenLeft != 0); + -- rParentScopeExecPl.loopChildrenLeft; + + if (rParentScopeExecPl.loopChildrenLeft) + { + loop_scope_done(tasks, graph, rExec, rParentScopeExecPl, parentScopePl, parentScopeTreePos); + } + } +} + +static void loop_done(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, ExecPipeline &rExecPl, PipelineId const pipeline) +{ + rExecPl.stage = lgrn::id_null(); + + PipelineTreePos_t scopeTreePos = graph.pipelineToLoopScope[pipeline]; + PipelineId scopePl = graph.pltreeToPipeline[scopeTreePos]; + ExecPipeline &rScopeExecPl = rExec.plData[scopePl]; + + if (scopePl != pipeline) // if not a loopscope itself + { + LGRN_ASSERT(rScopeExecPl.loopChildrenLeft != 0); + -- rScopeExecPl.loopChildrenLeft; + } + + if (rScopeExecPl.loopChildrenLeft == 0 && rScopeExecPl.stage == lgrn::id_null()) + { + loop_scope_done(tasks, graph, rExec, rScopeExecPl, scopePl, scopeTreePos); + } +} + +static void pipeline_advance_stage(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, PipelineId const pipeline) noexcept +{ + ExecPipeline &rExecPl = rExec.plData[pipeline]; + + LGRN_ASSERT(pipeline_can_advance(rExecPl)); + // * rExecPl.ownStageReqTasksLeft == 0; + // * rExecPl.tasksReqOwnStageLeft == 0; + + auto const stageCount = fanout_size(graph.pipelineToFirstAnystg, pipeline); + LGRN_ASSERTM(stageCount != 0, "Pipelines with 0 stages shouldn't be running"); + + bool const justStarting = rExecPl.stage == lgrn::id_null(); + + auto const nextStage = StageId( justStarting ? 0 : (int(rExecPl.stage)+1) ); + + rExecPl.tasksQueueDone = false; + + if (rExecPl.stage == rExecPl.waitStage) + { + rExecPl.waitSignaled = false; + } + + if (nextStage != StageId(stageCount)) + { + exec_log(rExec, ExecContext::StageChange{pipeline, rExecPl.stage, nextStage}); + + // Proceed to next stage since its valid + rExecPl.stage = nextStage; + } + else if ( rExecPl.loop ) + { + loop_done(tasks, graph, rExec, rExecPl, pipeline); + } + else + { + // Finished running + rExecPl.stage = lgrn::id_null(); + rExecPl.running = false; + rExecPl.canceled = false; + + LGRN_ASSERT(rExec.pipelinesRunning != 0); + -- rExec.pipelinesRunning; + + exec_log(rExec, ExecContext::PipelineFinish{pipeline}); + } +} + +static void pipeline_advance_reqs(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, PipelineId const pipeline) noexcept +{ + ExecPipeline &rExecPl = rExec.plData[pipeline]; + + if (rExecPl.stage == lgrn::id_null()) + { + return; + } + + AnyStageId const anystg = anystg_from(graph, pipeline, rExecPl.stage); + + // Evaluate Task-requires-Stages + // These are tasks from other pipelines that require nextStage + + auto const revTaskReqStageView = ArrayView(fanout_view(graph.anystgToFirstRevTaskreqstg, graph.revTaskreqstgToTask, anystg)); + + // Number of tasks that require this stage. This is decremented when required tasks finish + rExecPl.tasksReqOwnStageLeft = int(revTaskReqStageView.size()); + + for (TaskId const task : revTaskReqStageView) + { + if (rExec.tasksQueuedBlocked.contains(task)) + { + // Unblock tasks that are alredy queued + BlockedTask &rBlocked = rExec.tasksQueuedBlocked.get(task); + -- rBlocked.reqStagesLeft; + if (rBlocked.reqStagesLeft == 0) + { + exec_log(rExec, ExecContext::UnblockTask{task}); + ExecPipeline &rTaskPlExec = rExec.plData[rBlocked.pipeline]; + -- rTaskPlExec.tasksQueuedBlocked; + ++ rTaskPlExec.tasksQueuedRun; + rExec.tasksQueuedRun.emplace(task); + rExec.tasksQueuedBlocked.erase(task); + } + } + else + { + auto const [reqPipeline, reqStage] = tasks.m_taskRunOn[task]; + ExecPipeline &rReqExecPl = rExec.plData[reqPipeline]; + if ( ! rReqExecPl.running || rReqExecPl.canceled) + { + // Task is done or cancelled + -- rExecPl.tasksReqOwnStageLeft; + } + } + } + + // Evaluate Stage-requires-Tasks + // To be allowed to advance to the next-stage, these tasks must be complete. + + auto const stgreqtaskView = ArrayView(fanout_view(graph.anystgToFirstStgreqtask, graph.stgreqtaskData, anystg)); + + rExecPl.ownStageReqTasksLeft = int(stgreqtaskView.size()); + + // Decrement ownStageReqTasksLeft, as some of these tasks might already be complete + for (StageRequiresTask const& stgreqtask : stgreqtaskView) + { + ExecPipeline &rReqTaskExecPl = rExec.plData[stgreqtask.reqPipeline]; + + bool const reqTaskDone = [&tasks, &rReqTaskExecPl, &stgreqtask, &rExec] () noexcept -> bool + { + if ( ! rReqTaskExecPl.running ) + { + return true; // Not running, which means the whole pipeline finished already + } + else if (rReqTaskExecPl.canceled) + { + return true; // Stage cancelled. Required task is considered finish and will never run + } + else if (int(rReqTaskExecPl.stage) < int(stgreqtask.reqStage)) + { + return false; // Not yet reached required stage. Required task didn't run yet + } + else if (int(rReqTaskExecPl.stage) > int(stgreqtask.reqStage)) + { + return true; // Passed required stage. Required task finished + } + else if ( ! rReqTaskExecPl.tasksQueueDone ) + { + return false; // Required tasks not queued yet + } + else if ( rExec.tasksQueuedBlocked.contains(stgreqtask.reqTask) + || rExec.tasksQueuedRun .contains(stgreqtask.reqTask)) + { + return false; // Required task is queued and not yet finished running + } + else + { + return true; // On the right stage and task not running. This means it's done + } + }(); + + if (reqTaskDone) + { + -- rExecPl.ownStageReqTasksLeft; + } + } +} + +static void pipeline_advance_run(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, PipelineId const pipeline) noexcept +{ + ExecPipeline &rExecPl = rExec.plData[pipeline]; + + if (rExecPl.stage == lgrn::id_null()) + { + return; + } + + bool noTasksRun = true; + + if ( ! rExecPl.canceled ) + { + auto const anystg = anystg_from(graph, pipeline, rExecPl.stage); + auto const runTasks = ArrayView{fanout_view(graph.anystgToFirstRuntask, graph.runtaskToTask, anystg)}; + + noTasksRun = runTasks.size() == 0; + + for (TaskId task : runTasks) + { + LGRN_ASSERTM( ! rExec.tasksQueuedBlocked.contains(task), "Impossible to queue a task that's already queued"); + LGRN_ASSERTM( ! rExec.tasksQueuedRun .contains(task), "Impossible to queue a task that's already queued"); + + // Evaluate Task-requires-Stages + // Some requirements may already be satisfied + auto const taskreqstageView = ArrayView(fanout_view(graph.taskToFirstTaskreqstg, graph.taskreqstgData, task)); + auto reqStagesLeft = int(taskreqstageView.size()); + + for (TaskRequiresStage const& req : taskreqstageView) + { + ExecPipeline const &rReqPlData = rExec.plData[req.reqPipeline]; + + LGRN_ASSERTMV(rReqPlData.running, "Required pipelines must be running", TaskInt(task), PipelineInt(req.reqPipeline)); + + if (rReqPlData.stage == req.reqStage) + { + reqStagesLeft --; + } + } + + bool const blocked = reqStagesLeft != 0; + + if (blocked) + { + rExec.tasksQueuedBlocked.emplace(task, BlockedTask{reqStagesLeft, pipeline}); + ++ rExecPl.tasksQueuedBlocked; + } + else + { + rExec.tasksQueuedRun.emplace(task); + ++ rExecPl.tasksQueuedRun; + } + + exec_log(rExec, ExecContext::EnqueueTask{pipeline, rExecPl.stage, task, blocked}); + if (rExec.doLogging) + { + for (TaskRequiresStage const& req : taskreqstageView) + { + ExecPipeline const &rReqPlData = rExec.plData[req.reqPipeline]; + + exec_log(rExec, ExecContext::EnqueueTaskReq{req.reqPipeline, req.reqStage, rReqPlData.stage == req.reqStage}); + } + } + } + } + + rExecPl.tasksQueueDone = true; + + if (noTasksRun && pipeline_can_advance(rExecPl)) + { + // No tasks to run. RunTasks are responsible for setting this pipeline dirty once they're + // all done. If there is none, then this pipeline may get stuck if nothing sets it dirty, + // so set dirty right away. + rExec.plAdvanceNext.set(std::size_t(pipeline)); + rExec.hasPlAdvanceOrLoop = true; + } +} + +//----------------------------------------------------------------------------- + +// Pipeline utility + + +static void pipeline_cancel(Tasks const& tasks, TaskGraph const& graph, ExecContext& rExec, ExecPipeline& rExecPl, PipelineId pipeline) noexcept +{ + PipelineTreePos_t const treePos = graph.pipelineToPltree[pipeline]; + + auto const cancel_stage_ahead = [&tasks, &graph, &rExec, &rExecPl, pipeline] (AnyStageId const anystg) + { + for (TaskId task : fanout_view(graph.anystgToFirstRuntask, graph.runtaskToTask, anystg)) + { + // Stages depend on this RunTask (reverse Stage-requires-Task) + for (AnyStageId const& reqTaskAnystg : fanout_view(graph.taskToFirstRevStgreqtask, graph.revStgreqtaskToStage, task)) + { + PipelineId const reqPl = graph.anystgToPipeline[reqTaskAnystg]; + StageId const reqStg = stage_from(graph, reqPl, reqTaskAnystg); + ExecPipeline &rReqExecPl = rExec.plData[reqPl]; + + if (rReqExecPl.stage == reqStg) + { + // True if 'reqPl' sees that 'pipeline' is inside a loop, and may run more times + bool const loopsRelative = rExecPl.loop && is_pipeline_in_loop(tasks, graph, rExec, {.viewedFrom = reqPl, .insideLoop = pipeline}); + + if ( ! loopsRelative ) + { + LGRN_ASSERT(rReqExecPl.ownStageReqTasksLeft != 0); + -- rReqExecPl.ownStageReqTasksLeft; + pipeline_try_advance(rExec, rReqExecPl, reqPl); + } + } + } + + // RunTask depends on stages (Task-requires-Stage) + for (TaskRequiresStage const& req : fanout_view(graph.taskToFirstTaskreqstg, graph.taskreqstgData, task)) + { + ExecPipeline &rReqExecPl = rExec.plData[req.reqPipeline]; + + if (rReqExecPl.stage == req.reqStage) + { + bool const loopsRelative = rExecPl.loop && is_pipeline_in_loop(tasks, graph, rExec, {.viewedFrom = req.reqPipeline, .insideLoop = pipeline}); + + if ( ! loopsRelative ) + { + LGRN_ASSERT(rReqExecPl.tasksReqOwnStageLeft != 0); + -- rReqExecPl.tasksReqOwnStageLeft; + pipeline_try_advance(rExec, rReqExecPl, req.reqPipeline); + } + } + } + } + }; + + auto const cancel_stage_blocked = [&tasks, &graph, &rExec, &rExecPl, pipeline] (AnyStageId const anystg, [[maybe_unused]] int blockedExpected) + { + for (TaskId task : fanout_view(graph.anystgToFirstRuntask, graph.runtaskToTask, anystg)) + { + if ( ! rExec.tasksQueuedBlocked.contains(task) ) + { + continue; + } + + -- blockedExpected; + rExec.tasksQueuedBlocked.erase(task); + + // Stages depend on this RunTask (reverse Stage-requires-Task) + for (AnyStageId const& reqTaskAnystg : fanout_view(graph.taskToFirstRevStgreqtask, graph.revStgreqtaskToStage, task)) + { + PipelineId const reqPl = graph.anystgToPipeline[reqTaskAnystg]; + StageId const reqStg = stage_from(graph, reqPl, reqTaskAnystg); + ExecPipeline &rReqExecPl = rExec.plData[reqPl]; + + if (rReqExecPl.stage == reqStg) + { + // True if 'reqPl' sees that 'pipeline' is inside a loop, and may run more times + bool const loopsRelative = rExecPl.loop && is_pipeline_in_loop(tasks, graph, rExec, {.viewedFrom = reqPl, .insideLoop = pipeline}); + + if ( ! loopsRelative ) + { + LGRN_ASSERT(rReqExecPl.ownStageReqTasksLeft != 0); + -- rReqExecPl.ownStageReqTasksLeft; + pipeline_try_advance(rExec, rReqExecPl, reqPl); + } + } + } + + // RunTask depends on stages (Task-requires-Stage) + for (TaskRequiresStage const& req : fanout_view(graph.taskToFirstTaskreqstg, graph.taskreqstgData, task)) + { + ExecPipeline &rReqExecPl = rExec.plData[req.reqPipeline]; + + if (rReqExecPl.stage == req.reqStage) + { + bool const loopsRelative = rExecPl.loop && is_pipeline_in_loop(tasks, graph, rExec, {.viewedFrom = req.reqPipeline, .insideLoop = pipeline}); + + if ( ! loopsRelative ) + { + LGRN_ASSERT(rReqExecPl.tasksReqOwnStageLeft != 0); + -- rReqExecPl.tasksReqOwnStageLeft; + pipeline_try_advance(rExec, rReqExecPl, req.reqPipeline); + } + } + } + } + + LGRN_ASSERT(blockedExpected == 0); + }; + + subtree_for_each( + {.root = treePos, .includeRoot = true}, graph, rExec, + [&cancel_stage_ahead, &cancel_stage_blocked, &tasks, &graph, &rExec, &rExecPl, pipeline] + (PipelineTreePos_t const cancelPos, PipelineId const cancelPl, uint32_t const descendants) + { + ExecPipeline &rCancelExecPl = rExec.plData[cancelPl]; + + if ( ! rCancelExecPl.canceled ) + { + rCancelExecPl.canceled = true; + + if (rCancelExecPl.tasksQueuedBlocked != 0) + { + cancel_stage_blocked(anystg_from(graph, cancelPl, StageId(rCancelExecPl.stage)), rCancelExecPl.tasksQueuedBlocked); + rCancelExecPl.tasksQueuedBlocked = 0; + } + + auto const stageCount = int(fanout_size(graph.pipelineToFirstAnystg, cancelPl)); + auto const stagesAhead = int(rCancelExecPl.stage) + 1; + + // TODO: As of now, only stages ahead that have not yet run yet are being canceled. + // This is a problem for loops, where stages behind that have already run still + // have dependencies, since they may run again. Eventually deal with these. + + auto anystgInt = uint32_t(anystg_from(graph, cancelPl, StageId(stagesAhead))); + for (auto stgInt = stagesAhead; stgInt < stageCount; ++stgInt, ++anystgInt) + { + cancel_stage_ahead(AnyStageId(anystgInt)); + } + + // In this case, "stage == null" means the pipeline is done; advancing it would + // accidentally restart the pipeline. + if (rCancelExecPl.stage != lgrn::id_null()) + { + pipeline_try_advance(rExec, rCancelExecPl, cancelPl); + } + + exec_log(rExec, ExecContext::PipelineCancel{cancelPl, rCancelExecPl.stage}); + } + }); +} + +static void pipeline_try_advance(ExecContext &rExec, ExecPipeline &rExecPl, PipelineId const pipeline) noexcept +{ + if (pipeline_can_advance(rExecPl)) + { + rExec.plAdvance.set(std::size_t(pipeline)); + rExec.hasPlAdvanceOrLoop = true; + } +}; + +//----------------------------------------------------------------------------- + +// Read-only checks + +static constexpr bool pipeline_can_advance(ExecPipeline &rExecPl) noexcept +{ + // Pipeline can advance if... + return + rExecPl.running + + // Tasks required by stage are done + && rExecPl.ownStageReqTasksLeft == 0 + + // Not required by any tasks + && rExecPl.tasksReqOwnStageLeft == 0 + + // Tasks done + && ( rExecPl.tasksQueuedBlocked + rExecPl.tasksQueuedRun ) == 0 + + // Trigger checks out. only if... + && ( + // Wait is disabled + ( rExecPl.waitStage == lgrn::id_null() ) + + // Or not on specified wait stage + || ( rExecPl.waitStage != rExecPl.stage ) + + // Or wait is externally signaled to go + || rExecPl.waitSignaled + ); + +} + +/** + * @brief Check if a pipeline 'sees' another pipeline is enclosed within a non-cancelled loop + */ +static bool is_pipeline_in_loop(Tasks const& tasks, TaskGraph const& graph, ExecContext const& exec, ArgsForIsPipelineInLoop const args) noexcept +{ + PipelineTreePos_t const insideLoopScopePos = graph.pipelineToLoopScope[args.insideLoop]; + + if (insideLoopScopePos == lgrn::id_null()) + { + return false; // insideLoop is in the root, not inside a loop lol + } + + PipelineTreePos_t const viewedFromScopePos = graph.pipelineToLoopScope[args.viewedFrom]; + + if (insideLoopScopePos == viewedFromScopePos) + { + return false; // Both pipelines are in the same loop scope + } + else if (viewedFromScopePos == lgrn::id_null()) + { + // viewedFrom is in the root, and insideLoop is enclosed in a loop. + + if (exec.plData[args.insideLoop].canceled) + { + if (exec.plData[graph.pltreeToPipeline[insideLoopScopePos]].canceled) + { + return false; // insideLoop is canceled and will not loop again + } + else + { + return true; // insideLoop itself is canceled, but is parented to a non-canceled + // loop scope. It will loop more times. + } + } + else // insideLoop is canceled + { + return true; // insideLoop is not canceled and will loop more + } + } + else + { + // Check tree if insideLoopScopePos is a descendent of viewedFromScopePos + PipelineTreePos_t const ancestor = viewedFromScopePos; + PipelineTreePos_t const ancestorLastChild = viewedFromScopePos + 1 + graph.pltreeDescendantCounts[viewedFromScopePos]; + PipelineTreePos_t const descendent = insideLoopScopePos; + + bool const isDescendent = (ancestor < descendent) && (descendent < ancestorLastChild); + + if (isDescendent) + { + if (exec.plData[args.insideLoop].canceled) + { + if (exec.plData[graph.pltreeToPipeline[insideLoopScopePos]].canceled) + { + return false; // insideLoop is canceled and will not loop again + } + else + { + return true; // insideLoop is in a nested loop and is canceled, but is parented + // to a non-canceled loop scope. It will loop more times. + } + } + else + { + return true; // insideLoop is in a nested loop and is not canceled + } + } + else + { + return false; // viewedFrom and insideLoop are unrelated + } + } +} + +//----------------------------------------------------------------------------- + +// Minor utility + +void exec_conform(Tasks const& tasks, ExecContext &rOut) +{ + std::size_t const maxTasks = tasks.m_taskIds.capacity(); + std::size_t const maxPipeline = tasks.m_pipelineIds.capacity(); + + rOut.tasksQueuedRun .reserve(maxTasks); + rOut.tasksQueuedBlocked.reserve(maxTasks); + rOut.plData.resize(maxPipeline); + bitvector_resize(rOut.plAdvance, maxPipeline); + bitvector_resize(rOut.plAdvanceNext, maxPipeline); + bitvector_resize(rOut.plRequestRun, maxPipeline); + + for (PipelineInt const pipelineInt : tasks.m_pipelineIds.bitview().zeros()) + { + auto const pipeline = PipelineId(pipelineInt); + rOut.plData[pipeline].waitStage = tasks.m_pipelineControl[pipeline].waitStage; + } +} + +static void exec_log(ExecContext &rExec, ExecContext::LogMsg_t msg) noexcept +{ + if (rExec.doLogging) + { + rExec.logMsg.push_back(msg); + } +} + +template +static void subtree_for_each(ArgsForSubtreeForEach args, TaskGraph const& graph, ExecContext const& rExec, FUNC_T&& func) +{ + uint32_t const descendants = graph.pltreeDescendantCounts[args.root]; + PipelineTreePos_t const lastPos = args.root + 1 + descendants; + PipelineTreePos_t const firstPos = args.includeRoot ? args.root : (args.root+1); + + for (PipelineTreePos_t pos = firstPos; pos < lastPos; ++pos) + { + std::invoke(func, pos, graph.pltreeToPipeline[pos], descendants); + } +} + +} // namespace osp diff --git a/src/osp/tasks/execute.h b/src/osp/tasks/execute.h new file mode 100644 index 00000000..c79400cb --- /dev/null +++ b/src/osp/tasks/execute.h @@ -0,0 +1,242 @@ +/** + * Open Space Program + * Copyright © 2019-2022 Open Space Program Project + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#pragma once + +#include "tasks.h" +#include "worker.h" + +#include "../bitvector.h" + +#include + +#include +#include +#include + +namespace osp +{ + +/** + * @brief Per-Pipeline state needed for execution + */ +struct ExecPipeline +{ + + /// Number of Tasks with satisfied dependencies that are ready to run + int tasksQueuedRun { 0 }; + + ///< Number of Tasks waiting for their TaskReqStage dependencies to be satisfied + int tasksQueuedBlocked { 0 }; + + /** + * @brief Number of remaining TaskReqStage dependencies. + * + * Tasks from other running Pipelines require this Pipeline's current stage to be selected + * in order to run. This Pipeline has to wait for these tasks to complete before proceeding to + * the next stage. + */ + int tasksReqOwnStageLeft { 0 }; + + /** + * @brief Number of remaining StageReqTask dependencies. + * + * This Pipeline's current stage require Tasks from other Pipelines to complete in order to + * proceed to the next stage. + */ + int ownStageReqTasksLeft { 0 }; + + int loopChildrenLeft { 0 }; + + StageId stage { lgrn::id_null() }; + + StageId waitStage { lgrn::id_null() }; + bool waitSignaled { false }; + + bool tasksQueueDone { false }; + bool loop { false }; + bool running { false }; + bool canceled { false }; +}; + +struct BlockedTask +{ + int reqStagesLeft; + PipelineId pipeline; +}; + +struct LoopRequestRun +{ + PipelineId pipeline; + PipelineTreePos_t treePos; +}; + + +/** + * @brief Fast plain-old-data log for ExecContext state changes + */ +struct ExecLog +{ + struct UpdateStart { }; + struct UpdateCycle { }; + struct UpdateEnd { }; + + struct PipelineRun + { + PipelineId pipeline; + }; + + struct PipelineFinish + { + PipelineId pipeline; + }; + + struct PipelineCancel + { + PipelineId pipeline; + StageId stage; + }; + + struct PipelineLoop + { + PipelineId pipeline; + }; + + struct PipelineLoopFinish + { + PipelineId pipeline; + }; + + struct StageChange + { + PipelineId pipeline; + StageId stageOld; + StageId stageNew; + }; + + struct EnqueueTask + { + PipelineId pipeline; + StageId stage; + TaskId task; + bool blocked; + }; + + struct EnqueueTaskReq + { + PipelineId pipeline; + StageId stage; + bool satisfied; + }; + + struct UnblockTask + { + TaskId task; + }; + + struct CompleteTask + { + TaskId task; + }; + + struct ExternalRunRequest + { + PipelineId pipeline; + }; + + struct ExternalSignal + { + PipelineId pipeline; + bool ignored; + }; + + using LogMsg_t = std::variant< + UpdateStart, + UpdateCycle, + UpdateEnd, + PipelineRun, + PipelineFinish, + PipelineCancel, + PipelineLoop, + PipelineLoopFinish, + StageChange, + EnqueueTask, + EnqueueTaskReq, + UnblockTask, + CompleteTask, + ExternalRunRequest, + ExternalSignal>; + + std::vector logMsg; + bool doLogging{true}; +}; // struct ExecLog + +/** + * @brief State for executing Tasks and TaskGraph + */ +struct ExecContext : public ExecLog +{ + KeyedVec plData; + + entt::basic_sparse_set tasksQueuedRun; + entt::basic_storage tasksQueuedBlocked; + + BitVector_t plAdvance; + BitVector_t plAdvanceNext; + bool hasPlAdvanceOrLoop {false}; + + BitVector_t plRequestRun; + std::vector requestLoop; + bool hasRequestRun {false}; + + int pipelinesRunning {0}; + + // TODO: Consider multithreading. something something work stealing... + // * Allow multiple threads to search for and execute tasks. Atomic access + // for ExecContext? Might be messy to implement. + // * Only allow one thread to search for tasks, assign tasks to other + // threads if they're available before running own task. Another thread + // can take over once it completes its task. May be faster as only one + // thread is modifying ExecContext, and easier to implement. + // * Plug into an existing work queue library? + +}; // struct ExecContext + +void exec_conform(Tasks const& tasks, ExecContext &rOut); + +inline void exec_request_run(ExecContext &rExec, PipelineId pipeline) noexcept +{ + rExec.plRequestRun.set(std::size_t(pipeline)); + rExec.hasRequestRun = true; +} + +void exec_signal(ExecContext &rExec, PipelineId pipeline) noexcept; + +void exec_update(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec) noexcept; + +void complete_task(Tasks const& tasks, TaskGraph const& graph, ExecContext &rExec, TaskId task, TaskActions actions) noexcept; + + + +} // namespace osp diff --git a/src/osp/tasks/execute_simple.cpp b/src/osp/tasks/execute_simple.cpp deleted file mode 100644 index 80913acb..00000000 --- a/src/osp/tasks/execute_simple.cpp +++ /dev/null @@ -1,223 +0,0 @@ -/** - * Open Space Program - * Copyright © 2019-2022 Open Space Program Project - * - * MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "execute_simple.h" - -#include -#include - -using Corrade::Containers::ArrayView; -using Corrade::Containers::StridedArrayView2D; -using Corrade::Containers::arrayView; - -namespace osp -{ - -bool any_bits_match(BitSpanConst_t const lhs, BitSpanConst_t const rhs) -{ - auto itRhsInt = std::begin(rhs); - for (bit_int_t const lhsInt : lhs) - { - if ((lhsInt & (*itRhsInt)) != 0) - { - return true; - } - std::advance(itRhsInt, 1); - } - - return false; -} - -void task_enqueue(Tags const& tags, Tasks const& tasks, ExecutionContext &rExec, BitSpanConst_t const query) -{ - assert(query.size() == tags.tag_ints_per_task()); - - rExec.m_tagIncompleteCounts .resize(tags.m_tags.capacity(), 0); - rExec.m_tagRunningCounts .resize(tags.m_tags.capacity(), 0); - rExec.m_taskQueuedCounts .resize(tasks.m_tasks.capacity(), 0); - - task_for_each(tags, tasks, [&rExec, &query] - (uint32_t const currTask, ArrayView const currTags) - { - unsigned int &rQueuedCount = rExec.m_taskQueuedCounts[currTask]; - - if ( (rQueuedCount != 0) || ( ! any_bits_match(query, currTags) ) ) - { - return; // Ignore already-queued tasks, or if tags do not match query - } - - rQueuedCount = 1; // All good, now queue the task - - // Add the task's tags to the incomplete counts - auto const view = lgrn::bit_view(currTags); - for (uint32_t const tag : view.ones()) - { - rExec.m_tagIncompleteCounts[tag] ++; - } - }); -} - -void task_extern_set(ExecutionContext &rExec, TagId const tag, bool value) -{ - auto bitView = lgrn::bit_view(rExec.m_tagExternTriggers); - if (value) - { - bitView.set(std::size_t(tag)); - } - else - { - bitView.reset(std::size_t(tag)); - } -} - - -static bool tags_present(BitSpanConst_t const mask, BitSpanConst_t const taskTags) noexcept -{ - auto taskTagIntIt = std::begin(taskTags); - for (uint64_t const maskInt : mask) - { - uint64_t const taskTagInt = *taskTagIntIt; - - // No match if a 1 bit in taskTagInt corresponds with a 0 in maskInt - if ((maskInt & taskTagInt) != taskTagInt) - { - return false; - } - std::advance(taskTagIntIt, 1); - } - return true; -} - -void task_list_available(Tags const& tags, Tasks const& tasks, ExecutionContext const& exec, BitSpan_t tasksOut) -{ - assert(tasksOut.size() == tasks.m_tasks.vec().size()); - - // Bitmask makes it easy to compare the tags of a task - // 1 = allowed (default), 0 = not allowed - // All tags of a task must be allowed for the entire task to run. - // aka: All ones in a task's bits must corrolate to a one in the mask - std::size_t const maskSize = tags.m_tags.vec().size(); - std::vector mask(maskSize, ~uint64_t(0)); - auto maskBits = lgrn::bit_view(mask); - - auto tagDepends2d = tag_depends_2d(tags); - - // Check dependencies of each tag - // Set them 0 (disallow) in the mask if the tag has incomplete dependencies - for (uint32_t const currTag : tags.m_tags.bitview().zeros()) - { - bool satisfied = true; - auto const currTagDepends = tagDepends2d[currTag].asContiguous(); - - for (TagId const dependTag : currTagDepends) - { - if (dependTag == lgrn::id_null()) - { - break; - } - - if (exec.m_tagIncompleteCounts[std::size_t(dependTag)] != 0) - { - satisfied = false; - break; - } - } - - if ( ! satisfied) - { - maskBits.reset(currTag); - } - } - - // Check external dependencies - std::size_t const externSize = std::min({tags.m_tagExtern.size(), exec.m_tagExternTriggers.size(), maskSize}); - for (std::size_t i = 0; i < externSize; ++i) - { - mask[i] &= ~(tags.m_tagExtern[i] & exec.m_tagExternTriggers[i]); - } - - // TODO: Check Limits with exec.m_tagRunningCounts - - auto tasksOutBits = lgrn::bit_view(tasksOut); - - std::size_t const tagIntSize = tags.tag_ints_per_task(); - BitSpanConst_t const taskTagInts = Corrade::Containers::arrayView(tasks.m_taskTags); - - // Iterate all tasks and use mask to match which ones can run - for (uint32_t const currTask : tasks.m_tasks.bitview().zeros()) - { - if (exec.m_taskQueuedCounts[currTask] == 0) - { - continue; // Task not queued to run - } - - std::size_t const offset = currTask * tagIntSize; - auto const currTaskTagInts = taskTagInts.slice(offset, offset + tagIntSize); - - if (tags_present(mask, currTaskTagInts)) - { - tasksOutBits.set(currTask); - } - } -} - -void task_start(Tags const& tags, Tasks const& tasks, ExecutionContext &rExec, TaskId const task) -{ - auto currTaskTagInts = task_tags_2d(tags, tasks)[std::size_t(task)].asContiguous(); - - auto const view = lgrn::bit_view(currTaskTagInts); - for (uint32_t const tag : view.ones()) - { - rExec.m_tagRunningCounts[tag] ++; - } - - rExec.m_taskQueuedCounts[std::size_t(task)] --; -} - -void task_finish(Tags const& tags, Tasks const& tasks, ExecutionContext &rExec, TaskId const task) -{ - auto const externBits = lgrn::bit_view(tags.m_tagExtern); - - auto taskTagInts = task_tags_2d(tags, tasks)[std::size_t(task)].asContiguous(); - auto const taskTags = lgrn::bit_view(taskTagInts); - - for (uint32_t const tag : taskTags.ones()) - { - rExec.m_tagRunningCounts[tag] --; - rExec.m_tagIncompleteCounts[tag] --; - - // If last task to finish with this tag - if (rExec.m_tagIncompleteCounts[tag] == 0) - { - // Reset external dependency bit - if (externBits.test(tag)) - { - task_extern_set(rExec, TagId(tag), false); - } - } - } -} - -} // namespace osp diff --git a/src/osp/tasks/execute_simple.h b/src/osp/tasks/execute_simple.h deleted file mode 100644 index 62309f52..00000000 --- a/src/osp/tasks/execute_simple.h +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Open Space Program - * Copyright © 2019-2022 Open Space Program Project - * - * MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#pragma once - -#include "tasks.h" - -#include -#include - -#include -#include - -namespace osp -{ - -using BitSpan_t = Corrade::Containers::ArrayView; -using BitSpanConst_t = Corrade::Containers::ArrayView; - -/** - * @brief Convert a range of ints or enums to bit positions - * - * {0, 1, 7, 4} -> 0x1010011 - * - * @param range [in] Range of ints or enum classes - * @param bitsOut [out] Span of ints large enough to fit all bits in range - * - * @return bitsOut - */ -template -BitSpan_t to_bitspan(RANGE_T const& range, BitSpan_t bitsOut) noexcept -{ - auto outBits = lgrn::bit_view(bitsOut); - outBits.reset(); - for (auto const pos : range) - { - outBits.set(std::size_t(pos)); - } - return bitsOut; -} - -template -BitSpan_t to_bitspan(std::initializer_list tags, BitSpan_t out) noexcept -{ - return to_bitspan(Corrade::Containers::arrayView(tags), out); -} - -template -constexpr void task_for_each(Tags const& tags, Tasks const& tasks, FUNC_T&& func) -{ - auto const taskTags2d = task_tags_2d(tags, tasks); - - for (uint32_t const currTask : tasks.m_tasks.bitview().zeros()) - { - func(currTask, taskTags2d[currTask].asContiguous()); - } -} - -bool any_bits_match(BitSpanConst_t lhs, BitSpanConst_t rhs); - -/** - * @brief Enqueue all tasks that contain any specified tag - * - * @param tags [in] Tags - * @param tasks [in] Tasks - * @param rExec [ref] ExecutionContext to record queued tasks into - * @param query [in] Bit positions of tags used to select tasks - */ -void task_enqueue(Tags const& tags, Tasks const& tasks, ExecutionContext &rExec, BitSpanConst_t query); - -void task_extern_set(ExecutionContext &rExec, TagId tag, bool value); - -/** - * @brief List all available tasks that are currently allowed to run - * - * @param tags [in] Tags - * @param tasks [in] Tasks - * @param rExec [in] ExecutionContext indicating tasks available - * @param tasksOut [out] Available tasks as bit positions - */ -void task_list_available(Tags const& tags, Tasks const& tasks, ExecutionContext const& exec, BitSpan_t tasksOut); - -/** - * @brief Mark a task as running in an ExecutionContext - * - * @param tags [in] Tags - * @param tasks [in] Tasks - * @param rExec [ref] ExecutionContext to record running tasks - * @param task [in] Id of task to start running - */ -void task_start(Tags const& tags, Tasks const& tasks, ExecutionContext &rExec, TaskId task); - -/** - * @brief Mark a task as finished in an ExecutionContext - * - * @param tags [in] Tags - * @param tasks [in] Tasks - * @param rExec [ref] ExecutionContext to record running tasks - * @param task [in] Id of task to finish - */ -void task_finish(Tags const& tags, Tasks const& tasks, ExecutionContext &rExec, TaskId task); - -} // namespace osp diff --git a/src/osp/tasks/tasks.cpp b/src/osp/tasks/tasks.cpp new file mode 100644 index 00000000..535c4437 --- /dev/null +++ b/src/osp/tasks/tasks.cpp @@ -0,0 +1,394 @@ +/** + * Open Space Program + * Copyright © 2019-2023 Open Space Program Project + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "tasks.h" + +#include "../bitvector.h" + +#include + +namespace osp +{ + +struct TaskCounts +{ + uint16_t requiresStages {0}; + uint16_t requiredByStages {0}; +}; + +struct StageCounts +{ + uint16_t runTasks {0}; + uint16_t requiresTasks {0}; + uint16_t requiredByTasks {0}; +}; + +struct PipelineCounts +{ + std::array stageCounts; + + uint8_t stages { 0 }; + + PipelineId firstChild { lgrn::id_null() }; + PipelineId sibling { lgrn::id_null() }; +}; + + +TaskGraph make_exec_graph(Tasks const& tasks, ArrayView const data) +{ + TaskGraph out; + + std::size_t const maxPipelines = tasks.m_pipelineIds.capacity(); + std::size_t const maxTasks = tasks.m_taskIds.capacity(); + + KeyedVec plCounts; + KeyedVec taskCounts; + BitVector_t plInTree; + + out.pipelineToFirstAnystg .resize(maxPipelines); + bitvector_resize(plInTree, maxPipelines); + plCounts .resize(maxPipelines+1); + taskCounts .resize(maxTasks+1); + + std::size_t totalTasksReqStage = 0; + std::size_t totalStageReqTasks = 0; + std::size_t totalRunTasks = 0; + std::size_t totalStages = 0; + + // 1. Count total number of stages + + auto const count_stage = [&plCounts] (PipelineId const pipeline, StageId const stage) + { + uint8_t &rStageCount = plCounts[pipeline].stages; + rStageCount = std::max(rStageCount, uint8_t(uint8_t(stage) + 1)); + }; + + // Max 1 stage for each valid pipeline. Supporting 0-stage pipelines take more effort + for (PipelineInt const plInt : tasks.m_pipelineIds.bitview().zeros()) + { + plCounts[PipelineId(plInt)].stages = 1; + } + + // Count stages from task run-ons + for (TaskInt const taskInt : tasks.m_taskIds.bitview().zeros()) + { + auto const task = TaskId(taskInt); + auto const [runPipeline, runStage] = tasks.m_taskRunOn[task]; + + count_stage(runPipeline, runStage); + + ++ plCounts[runPipeline].stageCounts[std::size_t(runStage)].runTasks; + ++ totalRunTasks; + } + + // Count stages from syncs + for (TaskEdges const* pEdges : data) + { + for (auto const [task, pipeline, stage] : pEdges->m_syncWith) + { + count_stage(pipeline, stage); + } + } + + for (PipelineInt const plInt : tasks.m_pipelineIds.bitview().zeros()) + { + totalStages += plCounts[PipelineId(plInt)].stages; + } + + // 2. Count TaskRequiresStages and StageRequiresTasks + + for (TaskEdges const* pEdges : data) + { + for (auto const [task, pipeline, stage] : pEdges->m_syncWith) + { + StageCounts &rStageCounts = plCounts[pipeline].stageCounts[std::size_t(stage)]; + TaskCounts &rTaskCounts = taskCounts[task]; + + // TaskRequiresStage makes task require pipeline to be on stage to be allowed to run + ++ rTaskCounts .requiresStages; + ++ rStageCounts.requiredByTasks; + + + // StageRequiresTask for stage to wait for task to complete + ++ rStageCounts.requiresTasks; + ++ rTaskCounts .requiredByStages; + + } + totalTasksReqStage += pEdges->m_syncWith.size(); + totalStageReqTasks += pEdges->m_syncWith.size(); + } + + // 3. Map out children and siblings in tree + + for (PipelineInt const childPlInt : tasks.m_pipelineIds.bitview().zeros()) + { + PipelineId const child = PipelineId(childPlInt); + PipelineId const parent = tasks.m_pipelineParents[child]; + + if (parent != lgrn::id_null()) + { + plInTree.set(std::size_t(parent)); + plInTree.set(std::size_t(child)); + + PipelineCounts &rChildCounts = plCounts[child]; + PipelineCounts &rParentCounts = plCounts[parent]; + + if (rParentCounts.firstChild != lgrn::id_null()) + { + rChildCounts.sibling = rParentCounts.firstChild; + } + + rParentCounts.firstChild = child; + } + } + + std::size_t const treeSize = plInTree.count(); + + // 4. Allocate + + // The +1 is needed for 1-to-many connections to store the total number of other elements they + // index. This also simplifies logic in fanout_view(...) + + out.pipelineToFirstAnystg .resize(maxPipelines+1, lgrn::id_null()); + out.anystgToPipeline .resize(totalStages+1, lgrn::id_null()); + out.anystgToFirstRuntask .resize(totalStages+1, lgrn::id_null()); + out.runtaskToTask .resize(totalRunTasks, lgrn::id_null()); + out.anystgToFirstStgreqtask .resize(totalStages+1, lgrn::id_null()); + out.stgreqtaskData .resize(totalStageReqTasks, {}); + out.taskToFirstRevStgreqtask .resize(maxTasks+1, lgrn::id_null()); + out.revStgreqtaskToStage .resize(totalStageReqTasks, lgrn::id_null()); + out.taskToFirstTaskreqstg .resize(maxTasks+1, lgrn::id_null()); + out.taskreqstgData .resize(totalTasksReqStage, {}); + out.anystgToFirstRevTaskreqstg .resize(totalStages+1, lgrn::id_null()); + out.revTaskreqstgToTask .resize(totalTasksReqStage, lgrn::id_null()); + out.pltreeDescendantCounts .resize(treeSize, 0); + out.pltreeToPipeline .resize(treeSize, lgrn::id_null()); + out.pipelineToPltree .resize(maxPipelines, lgrn::id_null()); + out.pipelineToLoopScope .resize(maxPipelines, lgrn::id_null()); + + // 5. Calculate one-to-many partitions + + fanout_partition( + out.pipelineToFirstAnystg, + [&plCounts] (PipelineId pl) { return plCounts[pl].stages; }, + [&out] (PipelineId pl, AnyStageId claimed) { out.anystgToPipeline[claimed] = pl; }); + + fanout_partition( + out.anystgToFirstRuntask, + [&plCounts, &out] (AnyStageId stg) + { + PipelineId const pl = out.anystgToPipeline[stg]; + if (pl == lgrn::id_null()) + { + return uint16_t(0); + } + StageId const stgLocal = stage_from(out, pl, stg); + return plCounts[pl].stageCounts[std::size_t(stgLocal)].runTasks; + }, + [] (AnyStageId, RunTaskId) { }); + + fanout_partition( + out.anystgToFirstStgreqtask, + [&plCounts, &out] (AnyStageId stg) + { + PipelineId const pl = out.anystgToPipeline[stg]; + if (pl == lgrn::id_null()) + { + return uint16_t(0); + } + StageId const stgLocal = stage_from(out, pl, stg); + return plCounts[pl].stageCounts[std::size_t(stgLocal)].requiresTasks; + }, + [&out] (AnyStageId stg, StageReqTaskId claimed) { out.stgreqtaskData[claimed].ownStage = stg; }); + fanout_partition( + out.taskToFirstRevStgreqtask, + [&taskCounts] (TaskId task) { return taskCounts[task].requiredByStages; }, + [&out] (TaskId, ReverseStageReqTaskId) { }); + + fanout_partition( + out.taskToFirstTaskreqstg, + [&taskCounts] (TaskId task) { return taskCounts[task].requiresStages; }, + [&out] (TaskId task, TaskReqStageId claimed) { out.taskreqstgData[claimed].ownTask = task; }); + fanout_partition( + out.anystgToFirstRevTaskreqstg, + [&plCounts, &out] (AnyStageId stg) + { + PipelineId const pl = out.anystgToPipeline[stg]; + if (pl == lgrn::id_null()) + { + return uint16_t(0); + } + StageId const stgLocal = stage_from(out, pl, stg); + return plCounts[pl].stageCounts[std::size_t(stgLocal)].requiredByTasks; + }, + [&out] (AnyStageId, ReverseTaskReqStageId) { }); + + // 6. Push + + for (TaskInt const taskInt : tasks.m_taskIds.bitview().zeros()) + { + auto const task = TaskId(taskInt); + auto const run = tasks.m_taskRunOn[task]; + auto const anystg = anystg_from(out, run.pipeline, run.stage); + StageCounts &rStageCounts = plCounts[run.pipeline].stageCounts[std::size_t(run.stage)]; + RunTaskId const runtask = id_from_count(out.anystgToFirstRuntask, anystg, rStageCounts.runTasks); + + out.runtaskToTask[runtask] = task; + + -- rStageCounts.runTasks; + } + + for (TaskEdges const* pEdges : data) + { + for (auto const [task, pipeline, stage] : pEdges->m_syncWith) + { + AnyStageId const anystg = anystg_from(out, pipeline, stage); + StageCounts &rStageCounts = plCounts[pipeline].stageCounts[std::size_t(stage)]; + TaskCounts &rTaskCounts = taskCounts[task]; + + auto const [taskPipeline, taskStage] = tasks.m_taskRunOn[task]; + + // Add StageReqTask (pipeline, stage) requires task + StageReqTaskId const stgReqTaskId = id_from_count(out.anystgToFirstStgreqtask, anystg, rStageCounts.requiresTasks); + ReverseStageReqTaskId const revStgReqTaskId = id_from_count(out.taskToFirstRevStgreqtask, task, rTaskCounts.requiredByStages); + + StageRequiresTask &rStgReqTask = out.stgreqtaskData[stgReqTaskId]; + + // rTaskReqStage.ownStage set previously + rStgReqTask.reqTask = task; + rStgReqTask.reqPipeline = taskPipeline; + rStgReqTask.reqStage = taskStage; + out.revStgreqtaskToStage[revStgReqTaskId] = anystg; + + -- rStageCounts.requiresTasks; + -- rTaskCounts.requiredByStages; + -- totalStageReqTasks; + + // Add TaskReqStage task requires (pipeline, stage) + TaskReqStageId const taskReqStgId = id_from_count(out.taskToFirstTaskreqstg, task, rTaskCounts.requiresStages); + ReverseTaskReqStageId const revTaskReqStgId = id_from_count(out.anystgToFirstRevTaskreqstg, anystg, rStageCounts.requiredByTasks); + + TaskRequiresStage &rTaskReqStage = out.taskreqstgData[taskReqStgId]; + + // rTaskReqStage.ownTask set previously + rTaskReqStage.reqStage = stage; + rTaskReqStage.reqPipeline = pipeline; + out.revTaskreqstgToTask[revTaskReqStgId] = task; + + -- rTaskCounts.requiresStages; + -- rStageCounts.requiredByTasks; + -- totalTasksReqStage; + } + } + + [[maybe_unused]] auto const all_counts_zero = [&] () + { + if ( totalStageReqTasks != 0 + || totalTasksReqStage != 0 ) + { + return false; + } + for (PipelineCounts const& plCount : plCounts) + { + for (StageCounts const& stgCount : plCount.stageCounts) + { + if ( stgCount.requiredByTasks != 0 + || stgCount.requiresTasks != 0 ) + { + return false; + } + } + } + for (TaskCounts const& taskCount : taskCounts) + { + if ( taskCount.requiredByStages != 0 + || taskCount.requiresStages != 0 ) + { + return false; + } + } + + return true; + }; + + LGRN_ASSERTM(all_counts_zero(), "Counts repurposed as items remaining, and must all be zero by the end here"); + + + // 7. Build Pipeline Tree + + auto const add_subtree = [&] (auto const& self, PipelineId const root, PipelineId const firstChild, PipelineTreePos_t const loopScope, PipelineTreePos_t const pos) -> uint32_t + { + bool const rootLoops = tasks.m_pipelineControl[root].isLoopScope; + PipelineTreePos_t newLoopScope = rootLoops ? pos : loopScope; + + out.pltreeToPipeline[pos] = root; + out.pipelineToPltree[root] = pos; + out.pipelineToLoopScope[root] = newLoopScope; + + uint32_t descendantCount = 0; + + PipelineId child = firstChild; + + PipelineTreePos_t childPos = pos + 1; + + while (child != lgrn::id_null()) + { + PipelineCounts const& rChildCounts = plCounts[child]; + + uint32_t const childDescendantCount = self(self, child, rChildCounts.firstChild, newLoopScope, childPos); + descendantCount += 1 + childDescendantCount; + + child = rChildCounts.sibling; + childPos += 1 + childDescendantCount; + } + + out.pltreeDescendantCounts[pos] = descendantCount; + + return descendantCount; + }; + + PipelineTreePos_t rootPos = 0; + + for (PipelineInt const pipelineInt : tasks.m_pipelineIds.bitview().zeros()) + { + auto const pipeline = PipelineId(pipelineInt); + if ( ! plInTree.test(pipelineInt) || tasks.m_pipelineParents[pipeline] != lgrn::id_null()) + { + continue; // Not in tree or not a root + } + + // For each root pipeline + + PipelineCounts const& rRootCounts = plCounts[pipeline]; + + uint32_t const rootDescendantCount = add_subtree(add_subtree, pipeline, rRootCounts.firstChild, lgrn::id_null(), rootPos); + + rootPos += 1 + rootDescendantCount; + } + + return out; +} + +} // namespace osp + diff --git a/src/osp/tasks/tasks.h b/src/osp/tasks/tasks.h index 9f0a69b2..957f262a 100644 --- a/src/osp/tasks/tasks.h +++ b/src/osp/tasks/tasks.h @@ -24,115 +24,311 @@ */ #pragma once +#include "../keyed_vector.h" +#include "../types.h" + #include +#include -#include +#include #include +#include +#include +#include #include +#define OSP_DECLARE_STAGE_NAMES(type, ...) \ + inline osp::ArrayView stage_names([[maybe_unused]] type _) noexcept \ + { \ + static auto const arr = std::initializer_list{__VA_ARGS__}; \ + return osp::arrayView(arr); \ + } + +#define OSP_DECLARE_STAGE_SCHEDULE(type, schedule_enum) \ + constexpr inline type stage_schedule([[maybe_unused]] type _) noexcept \ + { \ + return schedule_enum; \ + } + +#define OSP_DECLARE_STAGE_NO_SCHEDULE(type) \ + constexpr inline type stage_schedule([[maybe_unused]] type _) noexcept \ + { \ + return lgrn::id_null(); \ + } + namespace osp { -using bit_int_t = uint64_t; +constexpr std::size_t gc_maxStages = 16; -enum class TaskId : uint32_t {}; -enum class TagId : uint32_t {}; +using StageBits_t = std::bitset; -struct Tags +using TaskInt = uint32_t; +using PipelineInt = uint32_t; +using StageInt = uint8_t; +using SemaphoreInt = uint32_t; + +enum class TaskId : TaskInt { }; +enum class PipelineId : PipelineInt { }; +enum class StageId : StageInt { }; +enum class SemaphoreId : SemaphoreInt { }; + +//----------------------------------------------------------------------------- + +struct PipelineInfo { - lgrn::IdRegistryStl m_tags; + using stage_type_family_t = entt::family; + using stage_type_t = stage_type_family_t::value_type; + + static inline KeyedVec> sm_stageNames; - // Dependency tags restricts a task from running until all tasks containing - // tags it depends on are complete. - // 2D with m_tagDependsPerTag elements per row, [tag][dependency] - // lgrn::null used as null, or termination per-tag - std::vector m_tagDepends; - std::size_t m_tagDependsPerTag{8}; + template + static inline constexpr void register_stage_enum() + { + PipelineInfo::stage_type_t const type = PipelineInfo::stage_type_family_t::value; + PipelineInfo::sm_stageNames[type] = stage_names(STAGE_ENUM_T{}); + } - // Limit sets how many tasks using a certain tag can run simultaneously - std::vector m_tagLimits; + std::string_view name; + std::string_view category; + stage_type_t stageType { lgrn::id_null() }; +}; - // Restricts associated enqueued tasks from running until externally set - // Resets once all associated tasks are complete - std::vector m_tagExtern; +struct PipelineControl +{ + TaskId scheduler { lgrn::id_null() }; + StageId waitStage { lgrn::id_null() }; + bool isLoopScope { false }; +}; + +struct TplTaskPipelineStage +{ + TaskId task; + PipelineId pipeline; + StageId stage; +}; + +struct TplPipelineStage +{ + PipelineId pipeline; + StageId stage; +}; - [[nodiscard]] std::size_t tag_ints_per_task() const noexcept { return m_tags.vec().size(); } +struct TplTaskSemaphore +{ + TaskId task; + SemaphoreId semaphore; +}; -}; // struct Tags +//----------------------------------------------------------------------------- struct Tasks { - lgrn::IdRegistryStl m_tasks; + lgrn::IdRegistryStl m_taskIds; + lgrn::IdRegistryStl m_pipelineIds; + lgrn::IdRegistryStl m_semaIds; + + KeyedVec m_semaLimits; + + KeyedVec m_pipelineInfo; + KeyedVec m_pipelineParents; + KeyedVec m_pipelineControl; + + KeyedVec m_taskRunOn; +}; + +struct TaskEdges +{ + std::vector m_syncWith; + + //std::vector m_semaphoreEdges; +}; + +using PipelineTreePos_t = uint32_t; + +enum class AnyStageId : uint32_t { }; + +enum class RunTaskId : uint32_t { }; +enum class RunStageId : uint32_t { }; + +enum class StageReqTaskId : uint32_t { }; +enum class ReverseStageReqTaskId : uint32_t { }; + +enum class TaskReqStageId : uint32_t { }; +enum class ReverseTaskReqStageId : uint32_t { }; + +struct StageRequiresTask +{ + AnyStageId ownStage { lgrn::id_null() }; + + // Task needs to be complete for requirement to be satisfied + // All requirements must be satisfied to proceed to the next stage + TaskId reqTask { lgrn::id_null() }; + PipelineId reqPipeline { lgrn::id_null() }; + StageId reqStage { lgrn::id_null() }; +}; + +struct TaskRequiresStage +{ + TaskId ownTask { lgrn::id_null() }; + + // Pipeline must be on a certain stage for requirement to be satisfied. + // All requirements must be satisfied for the task to be unblocked + PipelineId reqPipeline { lgrn::id_null() }; + StageId reqStage { lgrn::id_null() }; +}; + +struct TaskGraph +{ + // Each pipeline has multiple stages. + // PipelineId <--> many AnyStageIds + KeyedVec pipelineToFirstAnystg; + KeyedVec anystgToPipeline; + + // Each stage has multiple tasks to run + // AnyStageId --> TaskInStageId --> many TaskId + KeyedVec anystgToFirstRuntask; + KeyedVec runtaskToTask; + + // Each stage has multiple entrance requirements. + // AnyStageId <--> many StageEnterReqId + KeyedVec anystgToFirstStgreqtask; + KeyedVec stgreqtaskData; + // Tasks need to know which stages refer to them + // TaskId --> ReverseStageReqId --> many AnyStageId + KeyedVec taskToFirstRevStgreqtask; + KeyedVec revStgreqtaskToStage; + + // Task requires pipelines to be on certain stages. + // TaskId <--> TaskReqId + KeyedVec taskToFirstTaskreqstg; + KeyedVec taskreqstgData; + // Stages need to know which tasks require them + // StageId --> ReverseTaskReqId --> many TaskId + KeyedVec anystgToFirstRevTaskreqstg; + KeyedVec revTaskreqstgToTask; + + // N-ary tree structure represented as an array of descendant counts. Each node's subtree of + // descendants is positioned directly after it within the array. + // Example for tree structure "A( B(C(D)), E(F(G(H,I))) )" + // * Descendant Count array: [A:8, B:2, C:1, D:0, E:4, F:3, G:2, H:0, I:0] + KeyedVec pltreeDescendantCounts; + KeyedVec pltreeToPipeline; + KeyedVec pipelineToPltree; + KeyedVec pipelineToLoopScope; + + // not yet used + //lgrn::IntArrayMultiMap taskAcquire; /// Tasks acquire (n) Semaphores + //lgrn::IntArrayMultiMap semaAcquiredBy; /// Semaphores are acquired by (n) Tasks + +}; // struct TaskGraph + - // Bit positions are used to store which tags a task contains. - // 2D with Tags::m_tags.vec().size() elements per row. [task][tagsInt] - // partitioned based on Tags::tag_ints_per_task(): AAAABBBBCCCCDDDD - std::vector m_taskTags; +TaskGraph make_exec_graph(Tasks const& tasks, ArrayView data); -}; // struct Tasks +inline TaskGraph make_exec_graph(Tasks const& tasks, std::initializer_list data) +{ + return make_exec_graph(tasks, arrayView(data)); +} -inline Corrade::Containers::StridedArrayView2D tag_depends_2d(Tags& rTags) noexcept +template +inline void fanout_partition(KeyedVec& rVec, GETSIZE_T&& get_size, CLAIM_T&& claim) noexcept { - return {Corrade::Containers::arrayView(rTags.m_tagDepends.data(), rTags.m_tagDepends.size()), - {rTags.m_tags.capacity(), rTags.m_tagDependsPerTag}}; + using key_int_t = lgrn::underlying_int_type_t; + using value_int_t = lgrn::underlying_int_type_t; + + value_int_t currentId = 0; + for (value_int_t i = 0; i < rVec.size(); ++i) + { + value_int_t const size = get_size(KEY_T(i)); + + rVec[KEY_T(i)] = VALUE_T(currentId); + + if (size != 0) + { + value_int_t const nextId = currentId + size; + + for (value_int_t j = currentId; j < nextId; ++j) + { + claim(KEY_T(i), VALUE_T(j)); + } + + currentId = nextId; + } + } } -inline Corrade::Containers::StridedArrayView2D tag_depends_2d(Tags const& rTags) noexcept +template +inline auto fanout_size(KeyedVec const& vec, KEY_T key) -> lgrn::underlying_int_type_t { - return {Corrade::Containers::arrayView(rTags.m_tagDepends.data(), rTags.m_tagDepends.size()), - {rTags.m_tags.capacity(), rTags.m_tagDependsPerTag}}; + using key_int_t = lgrn::underlying_int_type_t; + using value_int_t = lgrn::underlying_int_type_t; + + value_int_t const firstIdx = value_int_t(vec[key]); + value_int_t const lastIdx = value_int_t(vec[KEY_T(key_int_t(key) + 1)]); + + return lastIdx - firstIdx; } -inline Corrade::Containers::StridedArrayView2D task_tags_2d(Tags const& rTags, Tasks &rTasks) noexcept +template +inline decltype(auto) fanout_view(KeyedVec const& vec, KeyedVec const& access, KEY_T key) { - return {Corrade::Containers::arrayView(rTasks.m_taskTags.data(), rTasks.m_taskTags.size()), - {rTasks.m_tasks.capacity(), rTags.tag_ints_per_task()}}; + using key_int_t = lgrn::underlying_int_type_t; + using value_int_t = lgrn::underlying_int_type_t; + + value_int_t const firstIdx = value_int_t(vec[key]); + value_int_t const lastIdx = value_int_t(vec[KEY_T(key_int_t(key) + 1)]); + + return arrayView(access.data(), access.size()).slice(firstIdx, lastIdx); } -inline Corrade::Containers::StridedArrayView2D task_tags_2d(Tags const& rTags, Tasks const &rTasks) noexcept + +template +inline VALUE_T id_from_count(KeyedVec const& vec, KEY_T const key, lgrn::underlying_int_type_t const count) { - return {Corrade::Containers::arrayView(rTasks.m_taskTags.data(), rTasks.m_taskTags.size()), - {rTasks.m_tasks.capacity(), rTags.tag_ints_per_task()}}; + return VALUE_T( lgrn::underlying_int_type_t(vec[KEY_T(lgrn::underlying_int_type_t(key) + 1)]) - count ); } -template -struct TaskDataVec +inline AnyStageId anystg_from(TaskGraph const& graph, PipelineId const pl, StageId stg) noexcept { - std::vector m_taskData; + return AnyStageId(uint32_t(graph.pipelineToFirstAnystg[pl]) + uint32_t(stg)); +} -}; // struct TaskDataVec +inline StageId stage_from(TaskGraph const& graph, PipelineId const pl, AnyStageId const stg) noexcept +{ + return StageId(uint32_t(stg) - uint32_t(graph.pipelineToFirstAnystg[pl])); +} -template -void task_data(TaskDataVec &rData, TaskId const task, RHS_T&& rhs) +inline StageId stage_from(TaskGraph const& graph, AnyStageId const stg) noexcept { - rData.m_taskData.resize( - std::max(rData.m_taskData.size(), std::size_t(task) + 1)); - rData.m_taskData[std::size_t(task)] = std::forward(rhs); + return stage_from(graph, graph.anystgToPipeline[stg], stg); } -struct ExecutionContext -{ - // Tag counts from running or queued tasks; used for Dependencies - std::vector m_tagIncompleteCounts; - // Tag counts from running tasks; used for Limits - std::vector m_tagRunningCounts; - // External tags - std::vector m_tagExternTriggers; - - // Number of times each task is queued to run - std::vector m_taskQueuedCounts; - //std::vector m_taskQueuedBits; - - // TODO: Consider multithreading. something something work stealing... - // * Allow multiple threads to search for and execute tasks. Atomic access - // for ExecutionContext? Might be messy to implement. - // * Only allow one thread to search for tasks, assign tasks to other - // threads if they're available before running own task. Another thread - // can take over once it completes its task. May be faster as only one - // thread is modifying ExecutionContext, and easier to implement. - // * Plug into an existing work queue library? - -}; // struct ExecutionContext +template +struct PipelineDef +{ + constexpr PipelineDef() = default; + constexpr PipelineDef(std::string_view name) + : m_name{name} + { } + + operator PipelineId() const noexcept { return m_value; } + operator std::size_t() const noexcept { return std::size_t(m_value); } + + constexpr PipelineId& operator=(PipelineId const assign) noexcept { m_value = assign; return m_value; } + + constexpr TplPipelineStage tpl(ENUM_T stage) const noexcept { return { m_value, StageId(stage) }; } + + constexpr TplPipelineStage operator()(ENUM_T stage) const noexcept { return { m_value, StageId(stage) }; } + + std::string_view m_name; + + PipelineInfo::stage_type_t m_type { PipelineInfo::stage_type_family_t::value }; + + PipelineId m_value { lgrn::id_null() }; +}; + +using PipelineDefBlank_t = PipelineDef; } // namespace osp diff --git a/src/osp/tasks/top_execute.cpp b/src/osp/tasks/top_execute.cpp index 0e20783c..ebcc4e7b 100644 --- a/src/osp/tasks/top_execute.cpp +++ b/src/osp/tasks/top_execute.cpp @@ -24,162 +24,287 @@ */ #include "top_execute.h" #include "top_worker.h" -#include "execute_simple.h" - -#include "../logging.h" +#include "execute.h" #include #include #include +#include #include #include -using Corrade::Containers::ArrayView; -using Corrade::Containers::StridedArrayView2D; -using Corrade::Containers::arrayView; - namespace osp { -void top_run_blocking(Tags const& tags, Tasks const& tasks, TopTaskDataVec_t& rTaskData, ArrayView topData, ExecutionContext& rExec) +void top_run_blocking(Tasks const& tasks, TaskGraph const& graph, TopTaskDataVec_t& rTaskData, ArrayView topData, ExecContext& rExec, WorkerContext worker) { std::vector topDataRefs; - std::vector enqueue(tags.m_tags.vec().size()); - - std::vector tasksToRun(tasks.m_tasks.vec().size()); - - // Run until there's no tasks left to run while (true) { - std::fill(std::begin(tasksToRun), std::end(tasksToRun), 0u); - task_list_available(tags, tasks, rExec, tasksToRun); - - auto const tasksToRunBits = lgrn::bit_view(tasksToRun); - unsigned int const availableCount = tasksToRunBits.count(); + auto const runTasksLeft = rExec.tasksQueuedRun.size(); + //auto const blockedTasksLeft = rExec.tasksQueuedBlocked.size(); - if (availableCount == 0) + if (runTasksLeft != 0) { - break; - } + TaskId const task = rExec.tasksQueuedRun.at(0); + TopTask &rTopTask = rTaskData[task]; - // Choose first available task - auto const task = TaskId(*tasksToRunBits.ones().begin()); + topDataRefs.clear(); + topDataRefs.reserve(rTopTask.m_dataUsed.size()); + for (TopDataId const dataId : rTopTask.m_dataUsed) + { + topDataRefs.push_back((dataId != lgrn::id_null()) + ? topData[dataId].as_ref() + : entt::any{}); + } - task_start(tags, tasks, rExec, task); + bool const shouldRun = (rTopTask.m_func != nullptr); - TopTask &rTopTask = rTaskData.m_taskData.at(std::size_t(task)); + // Task function is called here + TaskActions const status = shouldRun ? rTopTask.m_func(worker, topDataRefs) : TaskActions{}; - topDataRefs.clear(); - topDataRefs.reserve(rTopTask.m_dataUsed.size()); - for (TopDataId const dataId : rTopTask.m_dataUsed) + complete_task(tasks, graph, rExec, task, status); + } + else { - - topDataRefs.push_back((dataId != lgrn::id_null()) - ? topData[dataId].as_ref() - : entt::any{}); + break; } - bool enqueueHappened = false; + exec_update(tasks, graph, rExec); + } +} - // Task actually runs here. Results are not yet used for anything. - rTopTask.m_func(WorkerContext{{}, {enqueue}, enqueueHappened}, topDataRefs); +static void write_task_requirements(std::ostream &rStream, Tasks const& tasks, TaskGraph const& graph, ExecContext const& exec, TaskId const task) +{ + auto const taskreqstageView = ArrayView(fanout_view(graph.taskToFirstTaskreqstg, graph.taskreqstgData, task)); + for (TaskRequiresStage const& req : taskreqstageView) + { + ExecPipeline const &reqPlData = exec.plData[req.reqPipeline]; + PipelineInfo const& info = tasks.m_pipelineInfo[req.reqPipeline]; + auto const stageNames = ArrayView{PipelineInfo::sm_stageNames[info.stageType]}; - if (enqueueHappened) + if (reqPlData.stage != req.reqStage) { - task_enqueue(tags, tasks, rExec, enqueue); - std::fill(std::begin(enqueue), std::end(enqueue), 0u); + rStream << "* Requires PL" << std::setw(3) << PipelineInt(req.reqPipeline) << " stage " << stageNames[std::size_t(req.reqStage)] << "\n"; } - - task_finish(tags, tasks, rExec, task); } } -void top_enqueue_quick(Tags const& tags, Tasks const& tasks, ExecutionContext& rExec, ArrayView tagsEnq) -{ - std::vector tagsToRun(tags.m_tags.vec().size()); - to_bitspan(tagsEnq, tagsToRun); - task_enqueue(tags, tasks, rExec, tagsToRun); -} -static void check_task_dependencies(Tags const& tags, Tasks const& tasks, TopTaskDataVec_t const& taskData, ExecutionContext const &rExec, std::vector &rPath, uint32_t const task, bool& rGood) +std::ostream& operator<<(std::ostream& rStream, TopExecWriteState const& write) { - if (rExec.m_taskQueuedCounts.at(task) == 0) + auto const& [tasks, taskData, graph, exec] = write; + + static constexpr int nameMinColumns = 50; + static constexpr int maxDepth = 4; + + rStream << "Pipeline/Tree | Status | Stages | Pipeline Names\n" + << "_________________________________________________________________________________________\n"; + + auto const write_pipeline = [&rStream, &tasks=tasks, &exec=exec, &graph=graph] (PipelineId const pipeline, int const depth) { - return; // Only consider queued tasks - } + ExecPipeline const &plExec = exec.plData[pipeline]; - rPath.push_back(task); + for (int i = 0; i < depth; ++i) + { + rStream << "- "; + } + + rStream << "PL" << std::setw(3) << std::left << PipelineInt(pipeline) << " "; + + for (int i = 0; i < (maxDepth - depth); ++i) + { + rStream << " "; + } - auto const taskTags2d = task_tags_2d(tags, tasks); - auto const tagDepends2d = tag_depends_2d(tags); + rStream << " | "; - std::vector dependencyTagInts(tags.m_tags.vec().size(), 0x0); - auto dependencyTagsBits = lgrn::bit_view(dependencyTagInts); + ExecPipeline const &execPl = exec.plData[pipeline]; - // Loop through each of this task's tags to get dependent tags - auto const taskTagsInts = taskTags2d[task].asContiguous(); - auto const view = lgrn::bit_view(taskTagsInts); - for (uint32_t const currTag : view.ones()) - { - auto const currTagDepends = tagDepends2d[currTag].asContiguous(); + bool const signalBlocked = (execPl.waitStage != lgrn::id_null()) + && (execPl.waitStage == execPl.stage) + && ( ! execPl.waitSignaled ); - for (TagId const dependTag : currTagDepends) + rStream << (execPl.running ? 'R' : '-') + << (execPl.loop ? 'L' : '-') + << (execPl.loopChildrenLeft != 0 ? 'O' : '-') + << (execPl.canceled ? 'C' : '-') + << (signalBlocked ? 'S' : '-') + << (execPl.tasksQueuedRun != 0 ? 'Q' : '-') + << (execPl.tasksQueuedBlocked != 0 ? 'B' : '-'); + + rStream << " | "; + + int const stageCount = fanout_size(graph.pipelineToFirstAnystg, pipeline); + + PipelineInfo const& info = tasks.m_pipelineInfo[pipeline]; + + int charsUsed = 7; // "PL###" + ": " + + if (info.stageType != lgrn::id_null()) { - if (dependTag == lgrn::id_null()) + auto const stageNames = ArrayView{PipelineInfo::sm_stageNames[info.stageType]}; + + for (int stage = 0; stage < std::min(stageNames.size(), stageCount); ++stage) { - break; + bool const sel = int(plExec.stage) == stage; + rStream << (sel ? '[' : ' ') + << stageNames[stage] + << (sel ? ']' : ' '); + + charsUsed += 2 + stageNames[stage].size(); } + } - dependencyTagsBits.set(std::size_t(dependTag)); + for (; charsUsed < nameMinColumns; ++charsUsed) + { + rStream << ' '; } - } - // Get all tasks that contain any of the dependent tags - for (uint32_t const currTask : tasks.m_tasks.bitview().zeros()) + rStream << " | " << (info.name.empty() ? "untitled or deleted" : info.name); + + rStream << "\n"; + }; + + auto const traverse = [&write_pipeline, &rStream, &graph=graph] (auto const& self, PipelineTreePos_t first, PipelineTreePos_t last, int depth) -> void { - if (any_bits_match(taskTags2d[currTask].asContiguous(), dependencyTagInts)) + uint32_t descendants = 0; + for (PipelineTreePos_t pos = first; pos != last; pos += 1 + descendants) { - auto const &last = std::end(rPath); - if (last == std::find(std::begin(rPath), last, currTask)) - { - check_task_dependencies(tags, tasks, taskData, rExec, rPath, currTask, rGood); - } - else - { - rGood = false; - std::ostringstream ss; - ss << "TopTask Circular Dependency Detected!\n"; - - for (uint32_t const pathTask : rPath) - { - ss << "* [" << pathTask << "]: " << taskData.m_taskData[pathTask].m_debugName << "\n"; - } - ss << "* Loops back to [" << currTask << "]\n"; - OSP_LOG_ERROR("{}", ss.str()); - } + descendants = graph.pltreeDescendantCounts[pos]; + + write_pipeline(graph.pltreeToPipeline[pos], depth); + + self(self, pos + 1, pos + 1 + descendants, depth + 1); + } + }; + + traverse(traverse, 0, graph.pltreeToPipeline.size(), 0); + + // Write pipelines that are not in the tree + for (PipelineInt const plInt : tasks.m_pipelineIds.bitview().zeros()) + { + auto const pipeline = PipelineId(plInt); + if (graph.pipelineToPltree[pipeline] == lgrn::id_null()) + { + write_pipeline(pipeline, 0); } } - rPath.pop_back(); + rStream << "*Status: [R: Running] [L: Looping] [O: Looping Children] [C: Canceled] [S: Signal Blocked] [Q: Has Queued Tasks To Run] [B: Queued Tasks Blocked]\n"; + + + for (auto const [task, block] : exec.tasksQueuedBlocked.each()) + { + rStream << "Task Blocked: " << "TASK" << TaskInt(task) << " - " << taskData[task].m_debugName << "\n"; + + write_task_requirements(rStream, tasks, graph, exec, task); + } + + return rStream; } -bool debug_top_print_deadlock(Tags const& tags, Tasks const& tasks, TopTaskDataVec_t const& taskData, ExecutionContext const &rExec) +std::ostream& operator<<(std::ostream& rStream, TopExecWriteLog const& write) { - std::vector path; + auto const& [tasks, taskData, graph, exec] = write; - bool good = true; + auto const stage_name = [&tasks=tasks] (PipelineId pl, StageId stg) -> std::string_view + { + if (stg != lgrn::id_null()) + { + PipelineInfo const& info = tasks.m_pipelineInfo[pl]; + auto const stageNames = ArrayView{PipelineInfo::sm_stageNames[info.stageType]}; + return stageNames[std::size_t(stg)]; + } + else + { + return "NULL"; + } + }; - // Iterate all tasks - for (uint32_t const currTask : tasks.m_tasks.bitview().zeros()) + auto const visitMsg = [&rStream, &tasks=tasks, &taskData=taskData, &graph=graph, &stage_name] (auto&& msg) { - check_task_dependencies(tags, tasks, taskData, rExec, path, currTask, good); + using MSG_T = std::decay_t; + if constexpr (std::is_same_v) + { + rStream << "UpdateStart\n"; + } + else if constexpr (std::is_same_v) + { + rStream << "UpdateCycle\n"; + } + else if constexpr (std::is_same_v) + { + rStream << "UpdateEnd\n"; + } + else if constexpr (std::is_same_v) + { + rStream << " PipelineRun PL" << std::setw(3) << std::left << PipelineInt(msg.pipeline) << "\n"; + } + else if constexpr (std::is_same_v) + { + rStream << " PipelineFinish PL" << std::setw(3) << std::left << PipelineInt(msg.pipeline) << "\n"; + } + else if constexpr (std::is_same_v) + { + rStream << " PipelineCancel PL" << std::setw(3) << std::left << PipelineInt(msg.pipeline) << "(" + << stage_name(msg.pipeline, msg.stage) << ")\n"; + } + else if constexpr (std::is_same_v) + { + rStream << " PipelineLoop PL" << std::setw(3) << std::left << PipelineInt(msg.pipeline) << "\n"; + } + else if constexpr (std::is_same_v) + { + rStream << " PipelineLoopFinish PL" << std::setw(3) << std::left << PipelineInt(msg.pipeline) << "\n"; + } + else if constexpr (std::is_same_v) + { + rStream << " StageChange PL" << std::setw(3) << std::left << PipelineInt(msg.pipeline) + << "(" << stage_name(msg.pipeline, msg.stageOld) << " -> " << stage_name(msg.pipeline, msg.stageNew) << ")\n"; + } + else if constexpr (std::is_same_v) + { + rStream << " Enqueue " << (msg.blocked ? "Blocked" : "Run") + << " on PL" << std::setw(3) << std::left << PipelineInt(msg.pipeline) + << "(" << stage_name(msg.pipeline, msg.stage) << ")" + << " TASK" << TaskInt(msg.task) << " - " << taskData[msg.task].m_debugName << "\n"; + } + else if constexpr (std::is_same_v) + { + rStream << " * " << (msg.satisfied ? "[DONE]" : "[wait]") << "Require PL" + << std::setw(3) << std::left << PipelineInt(msg.pipeline) + << "(" << stage_name(msg.pipeline, msg.stage) << ")\n"; + } + else if constexpr (std::is_same_v) + { + rStream << " * Unblock TASK" << TaskInt(msg.task) << "\n"; + } + else if constexpr (std::is_same_v) + { + rStream << "Complete TASK" << TaskInt(msg.task) << " - " << taskData[msg.task].m_debugName << "\n"; + } + else if constexpr (std::is_same_v) + { + rStream << "ExternalRunRequest PL" << std::setw(3) << std::left << PipelineInt(msg.pipeline) << "\n"; + } + else if constexpr (std::is_same_v) + { + rStream << "ExternalSignal PL" << std::setw(3) << std::left << PipelineInt(msg.pipeline) << (msg.ignored ? " IGNORED!" : " ") << "\n"; + } + }; + + for (ExecContext::LogMsg_t const& msg : exec.logMsg) + { + std::visit(visitMsg, msg); } - return good; + return rStream; } + } // namespace testapp diff --git a/src/osp/tasks/top_execute.h b/src/osp/tasks/top_execute.h index 2dd970de..35a74d73 100644 --- a/src/osp/tasks/top_execute.h +++ b/src/osp/tasks/top_execute.h @@ -24,6 +24,7 @@ */ #pragma once +#include "execute.h" #include "tasks.h" #include "top_tasks.h" @@ -32,16 +33,26 @@ namespace osp { +void top_run_blocking(Tasks const& tasks, TaskGraph const& graph, TopTaskDataVec_t& rTaskData, ArrayView topData, ExecContext& rExec, WorkerContext worker = {}); -void top_run_blocking(Tags const& tags, Tasks const& tasks, TopTaskDataVec_t& rTaskData, ArrayView topData, ExecutionContext& rExec); - -void top_enqueue_quick(Tags const& tags, Tasks const& tasks, ExecutionContext& rExec, ArrayView tagsEnq); +struct TopExecWriteState +{ + Tasks const &tasks; + TopTaskDataVec_t const &taskData; + TaskGraph const &graph; + ExecContext const &exec; +}; -inline void top_enqueue_quick(Tags const& tags, Tasks const& tasks, ExecutionContext& rExec, std::initializer_list tagsEnq) +struct TopExecWriteLog { - return top_enqueue_quick(tags, tasks, rExec, Corrade::Containers::arrayView(tagsEnq)); -} + Tasks const &tasks; + TopTaskDataVec_t const &taskData; + TaskGraph const &graph; + ExecContext const &exec; +}; + +std::ostream& operator<<(std::ostream& rStream, TopExecWriteState const& write); -bool debug_top_print_deadlock(Tags const& tags, Tasks const& tasks, TopTaskDataVec_t const& taskData, ExecutionContext const &rExec); +std::ostream& operator<<(std::ostream& rStream, TopExecWriteLog const& write); } // namespace testapp diff --git a/src/osp/tasks/top_session.cpp b/src/osp/tasks/top_session.cpp deleted file mode 100644 index bd6d93b5..00000000 --- a/src/osp/tasks/top_session.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Open Space Program - * Copyright © 2019-2022 Open Space Program Project - * - * MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include "top_session.h" -#include "top_execute.h" -#include "execute_simple.h" -#include "tasks.h" - -#include - -#include - -#include - -using Corrade::Containers::ArrayView; -using Corrade::Containers::StridedArrayView2D; -using Corrade::Containers::arrayView; - -namespace osp -{ - -void top_close_session(Tags& rTags, Tasks& rTasks, TopTaskDataVec_t& rTaskData, ArrayView topData, ExecutionContext& rExec, ArrayView sessions) -{ - // Run cleanup tasks - { - // Accumulate together all cleanup tags from all sessons - std::vector tagsToRun(rTags.m_tags.vec().size()); - auto tagsToRunBits = lgrn::bit_view(tagsToRun); - for (Session &rSession : sessions) - { - if (TagId const tgCleanupEvt = std::exchange(rSession.m_tgCleanupEvt, lgrn::id_null()); - tgCleanupEvt != lgrn::id_null()) - { - tagsToRunBits.set(std::size_t(tgCleanupEvt)); - } - } - - task_enqueue(rTags, rTasks, rExec, tagsToRun); - top_run_blocking(rTags, rTasks, rTaskData, topData, rExec); - } - - // Clear each session's TopData - for (Session &rSession : sessions) - { - for (TopDataId const id : std::exchange(rSession.m_dataIds, {})) - { - if (id != lgrn::id_null()) - { - topData[std::size_t(id)].reset(); - } - } - } - - auto const tagInts2d = task_tags_2d(rTags, rTasks); - - // Clear each session's tasks - for (Session &rSession : sessions) - { - for (TaskId const task : rSession.m_taskIds) - { - rTasks.m_tasks.remove(task); - - auto taskTagInts = tagInts2d[std::size_t(task)].asContiguous(); - lgrn::bit_view(taskTagInts).reset(); - - TopTask &rCurrTaskData = rTaskData.m_taskData[std::size_t(task)]; - rCurrTaskData.m_dataUsed.clear(); - rCurrTaskData.m_func = nullptr; - } - } - - auto const depends2d = tag_depends_2d(rTags); - - // Clear each session's tags - for (Session &rSession : sessions) - { - for (TagId const tag : std::exchange(rSession.m_tagIds, {})) - { - if (tag != lgrn::id_null()) - { - rTags.m_tags.remove(tag); - - auto tagDepends = depends2d[std::size_t(tag)].asContiguous(); - std::fill(std::begin(tagDepends), std::end(tagDepends), lgrn::id_null()); - } - } - } -} - -} // namespace testapp diff --git a/src/osp/tasks/top_session.h b/src/osp/tasks/top_session.h index 32472d06..0a882099 100644 --- a/src/osp/tasks/top_session.h +++ b/src/osp/tasks/top_session.h @@ -24,35 +24,46 @@ */ #pragma once -#include "top_tasks.h" -#include "top_utils.h" #include "tasks.h" +#include "top_utils.h" #include #include #include +#include #include #include #include -#define OSP_STRUCT_BIND_C(outerFunc, inner, count, ...) auto const [__VA_ARGS__] = outerFunc(inner) -#define OSP_STRUCT_BIND_B(x) x -#define OSP_STRUCT_BIND_A(...) OSP_STRUCT_BIND_B(OSP_STRUCT_BIND_C(__VA_ARGS__)) +#define OSP_AUX_DCDI_C(session, topData, count, ...) \ + session.m_data.resize(count); \ + osp::top_reserve(topData, 0, session.m_data.begin(), session.m_data.end()); \ + auto const [__VA_ARGS__] = osp::unpack(session.m_data) +#define OSP_AUX_DCDI_B(x) x +#define OSP_AUX_DCDI_A(...) OSP_AUX_DCDI_B(OSP_AUX_DCDI_C(__VA_ARGS__)) + +/** + * @brief + */ +#define OSP_DECLARE_CREATE_DATA_IDS(session, topData, arglist) OSP_AUX_DCDI_A(session, topData, arglist); + + +#define OSP_AUX_DGDI_C(session, count, ...) \ + auto const [__VA_ARGS__] = osp::unpack(session.m_data) +#define OSP_AUX_DGDI_B(x) x +#define OSP_AUX_DGDI_A(...) OSP_AUX_DGDI_B(OSP_AUX_DGDI_C(__VA_ARGS__)) + +#define OSP_DECLARE_GET_DATA_IDS(session, arglist) OSP_AUX_DGDI_A(session, arglist); -#define OSP_SESSION_UNPACK_TAGS(session, name) OSP_STRUCT_BIND_A(osp::unpack, (session).m_tagIds, OSP_TAGS_##name); -#define OSP_SESSION_UNPACK_DATA(session, name) OSP_STRUCT_BIND_A(osp::unpack, (session).m_dataIds, OSP_DATA_##name); -#define OSP_SESSION_ACQUIRE_TAGS(session, tags, name) OSP_STRUCT_BIND_A((session).acquire_tags, (tags), OSP_TAGS_##name); -#define OSP_SESSION_ACQUIRE_DATA(session, topData, name) OSP_STRUCT_BIND_A((session).acquire_data, (topData), OSP_DATA_##name); namespace osp { /** - * @brief A convenient group of TopData, Tasks, and Tags that work together to - * support a certain feature. + * @brief A convenient group of Pipelines that work together to support a certain feature. * * Sessions only store vectors of integer IDs, and don't does not handle * ownership on its own. Close using osp::top_close_session before destruction. @@ -60,41 +71,70 @@ namespace osp struct Session { template - std::array acquire_data(ArrayView topData) + [[nodiscard]] std::array acquire_data(ArrayView topData) { std::array out; top_reserve(topData, 0, std::begin(out), std::end(out)); - m_dataIds.assign(std::begin(out), std::end(out)); + m_data.assign(std::begin(out), std::end(out)); return out; } - template - std::array acquire_tags(Tags &rTags) + template + TGT_STRUCT_T create_pipelines(BUILDER_T &rBuilder) { - std::array out; - rTags.m_tags.create(std::begin(out), std::end(out)); - m_tagIds.assign(std::begin(out), std::end(out)); - return out; + static_assert(sizeof(TGT_STRUCT_T) % sizeof(PipelineDefBlank_t) == 0); + constexpr std::size_t count = sizeof(TGT_STRUCT_T) / sizeof(PipelineDefBlank_t); + + std::type_info const& info = typeid(TGT_STRUCT_T); + m_structHash = info.hash_code(); + m_structName = info.name(); + + m_pipelines.resize(count); + + return rBuilder.template create_pipelines(ArrayView(m_pipelines.data(), count)); } - TaskId& task() + template + [[nodiscard]] TGT_STRUCT_T get_pipelines() const { - return m_taskIds.emplace_back(lgrn::id_null()); + static_assert(sizeof(TGT_STRUCT_T) % sizeof(PipelineId) == 0); + constexpr std::size_t count = sizeof(TGT_STRUCT_T) / sizeof(PipelineDefBlank_t); + + [[maybe_unused]] std::type_info const& info = typeid(TGT_STRUCT_T); + LGRN_ASSERTMV(m_structHash == info.hash_code() && count == m_pipelines.size(), + "get_pipeline must use the same struct previously given to get_pipelines", + m_structHash, m_structName, + info.hash_code(), info.name(), + m_pipelines.size()); + + alignas(TGT_STRUCT_T) std::array bytes; + TGT_STRUCT_T *pOut = new(bytes.data()) TGT_STRUCT_T;\ + + for (std::size_t i = 0; i < count; ++i) + { + unsigned char *pDefBytes = bytes.data() + sizeof(PipelineDefBlank_t)*i; + *reinterpret_cast(pDefBytes + offsetof(PipelineDefBlank_t, m_value)) = m_pipelines[i]; + } + + return *pOut; } - std::vector m_dataIds; - std::vector m_tagIds; - std::vector m_taskIds; + std::vector m_data; + std::vector m_pipelines; + std::vector m_tasks; - TagId m_tgCleanupEvt{lgrn::id_null()}; -}; + PipelineId m_cleanup { lgrn::id_null() }; -using Sessions_t = std::vector; + std::size_t m_structHash{0}; + std::string m_structName; -/** - * @brief Close sessions, delete all their associated TopData, Tasks, and Tags. - */ -void top_close_session(Tags& rTags, Tasks& rTasks, TopTaskDataVec_t& rTaskData, ArrayView topData, ExecutionContext& rExec, ArrayView sessions); +}; // struct Session + +struct SessionGroup +{ + std::vector m_sessions; + TaskEdges m_edges; +}; } // namespace osp diff --git a/src/osp/tasks/top_tasks.h b/src/osp/tasks/top_tasks.h index d8a38dd1..9ef585a4 100644 --- a/src/osp/tasks/top_tasks.h +++ b/src/osp/tasks/top_tasks.h @@ -33,30 +33,13 @@ namespace osp { -// TODO: ignore debug names on release builds - struct TopTask { - std::string m_debugName; - std::vector m_dataUsed; - TopTaskFunc_t m_func{nullptr}; + std::string m_debugName; + std::vector m_dataUsed; + TopTaskFunc_t m_func { nullptr }; }; -inline void task_data(TaskDataVec &rData, TaskId const task, std::string_view debugName, std::initializer_list dataUsed, TopTaskFunc_t func) -{ - rData.m_taskData.resize( - std::max(rData.m_taskData.size(), std::size_t(task) + 1)); - auto &rTopTask = rData.m_taskData[std::size_t(task)]; - rTopTask.m_debugName = debugName; - rTopTask.m_dataUsed = dataUsed; - rTopTask.m_func = func; -} - -inline void task_data(TaskDataVec &rData, TaskId const task, std::initializer_list dataUsed, TopTaskFunc_t func) -{ - task_data(rData, task, "Untitled Top Task", dataUsed, func); -} - -using TopTaskDataVec_t = TaskDataVec; +using TopTaskDataVec_t = KeyedVec; } // namespace osp diff --git a/src/osp/tasks/top_utils.h b/src/osp/tasks/top_utils.h index c01f12b0..2367af8d 100644 --- a/src/osp/tasks/top_utils.h +++ b/src/osp/tasks/top_utils.h @@ -25,15 +25,18 @@ #pragma once #include "tasks.h" +#include "builder.h" +#include "top_tasks.h" #include "top_worker.h" #include +#include + #include #include #include #include -#include namespace osp { @@ -101,7 +104,7 @@ template return entt::any_cast(topData[id]); } - +//----------------------------------------------------------------------------- template struct wrap_args_trait @@ -115,6 +118,7 @@ struct wrap_args_trait } else { + LGRN_ASSERTMV(topData.size() > argIndex, "Task function has more arguments than TopDataIds provided", topData.size(), argIndex); return entt::any_cast(topData[argIndex]); } } @@ -122,19 +126,18 @@ struct wrap_args_trait template static constexpr decltype(auto) cast_args(ArrayView topData, WorkerContext ctx, [[maybe_unused]] std::index_sequence indices) noexcept { - assert(topData.size() == sizeof...(ARGS_T)); return FUNCTOR_T{}(cast_arg(topData, ctx, INDEX_T) ...); } template - static TopTaskStatus wrapped_task([[maybe_unused]] WorkerContext ctx, ArrayView topData) noexcept + static TaskActions wrapped_task([[maybe_unused]] WorkerContext ctx, ArrayView topData) noexcept { if constexpr (std::is_void_v) { cast_args(topData, ctx, std::make_index_sequence{}); - return TopTaskStatus::Success; + return {}; } - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) { return cast_args(topData, ctx, std::make_index_sequence{}); } @@ -176,5 +179,94 @@ constexpr TopTaskFunc_t wrap_args(FUNC_T funcArg) return wrap_args_trait::unpack(functionPtr); } +//----------------------------------------------------------------------------- + +struct TopTaskBuilder; +struct TopTaskTaskRef; + +struct TopTaskBuilderTraits +{ + using Builder_t = TopTaskBuilder; + using TaskRef_t = TopTaskTaskRef; + + template + using PipelineRef_t = PipelineRefBase; +}; + + +/** + * @brief Convenient interface for building TopTasks + */ +struct TopTaskBuilder : public TaskBuilderBase +{ + TopTaskBuilder(Tasks& rTasks, TaskEdges& rEdges, TopTaskDataVec_t& rData) + : TaskBuilderBase( {rTasks, rEdges} ) + , m_rData{rData} + { } + TopTaskBuilder(TopTaskBuilder const& copy) = delete; + TopTaskBuilder(TopTaskBuilder && move) = default; + + TopTaskBuilder& operator=(TopTaskBuilder const& copy) = delete; + + TopTaskDataVec_t & m_rData; +}; + +struct TopTaskTaskRef : public TaskRefBase +{ + inline TopTaskTaskRef& name(std::string_view debugName); + inline TopTaskTaskRef& args(std::initializer_list dataUsed); + + template + TopTaskTaskRef& func(FUNC_T&& funcArg); + inline TopTaskTaskRef& func_raw(TopTaskFunc_t func); + + inline TopTaskTaskRef& important_deps_count(int value); + + template + TopTaskTaskRef& push_to(CONTAINER_T& rContainer); +}; + +TopTaskTaskRef& TopTaskTaskRef::name(std::string_view debugName) +{ + m_rBuilder.m_rData.resize(m_rBuilder.m_rTasks.m_taskIds.capacity()); + m_rBuilder.m_rData[m_taskId].m_debugName = debugName; + return *this; +} + +TopTaskTaskRef& TopTaskTaskRef::args(std::initializer_list dataUsed) +{ + m_rBuilder.m_rData.resize(m_rBuilder.m_rTasks.m_taskIds.capacity()); + m_rBuilder.m_rData[m_taskId].m_dataUsed = dataUsed; + return *this; +} + +template +TopTaskTaskRef& TopTaskTaskRef::func(FUNC_T&& funcArg) +{ + m_rBuilder.m_rData.resize(m_rBuilder.m_rTasks.m_taskIds.capacity()); + m_rBuilder.m_rData[m_taskId].m_func = wrap_args(funcArg); + return *this; +} + +TopTaskTaskRef& TopTaskTaskRef::func_raw(TopTaskFunc_t func) +{ + m_rBuilder.m_rData.resize(m_rBuilder.m_rTasks.m_taskIds.capacity()); + m_rBuilder.m_rData[m_taskId].m_func = func; + return *this; +} + +//TopTaskRef& TopTaskRef::aware_of_dirty_depends(bool value) +//{ +// m_rBuilder.m_rData.resize(m_rBuilder.m_rTasks.m_taskIds.capacity()); +// m_rBuilder.m_rData[m_taskId].m_awareOfDirtyDeps = value; +// return *this; +//} + +template +TopTaskTaskRef& TopTaskTaskRef::push_to(CONTAINER_T& rContainer) +{ + rContainer.push_back(m_taskId); + return *this; +} } // namespace osp diff --git a/src/osp/tasks/top_worker.h b/src/osp/tasks/top_worker.h index e023ba88..8b647f8b 100644 --- a/src/osp/tasks/top_worker.h +++ b/src/osp/tasks/top_worker.h @@ -24,19 +24,20 @@ */ #pragma once -#include +#include "worker.h" + +#include "../types.h" #include +#include + +#include #include namespace osp { -using bit_int_t = uint64_t; - -using Corrade::Containers::ArrayView; - using TopDataId = uint32_t; using TopDataIds_t = std::initializer_list; @@ -44,25 +45,9 @@ struct Reserved {}; struct WorkerContext { - struct LimitSlot - { - uint32_t m_tag; - int m_slot; - }; - - Corrade::Containers::ArrayView m_limitSlots; - Corrade::Containers::ArrayView m_enqueue; - - bool & m_rEnqueueHappened; -}; - -enum class TopTaskStatus : uint8_t -{ - Success = 0, - Failed = 1, - DidNothing = 2 + //DependOnDirty_t m_dependOnDirty; }; -using TopTaskFunc_t = TopTaskStatus(*)(WorkerContext, ArrayView) noexcept; +using TopTaskFunc_t = TaskActions(*)(WorkerContext, ArrayView) noexcept; } // namespace osp diff --git a/src/osp/tasks/worker.h b/src/osp/tasks/worker.h new file mode 100644 index 00000000..66b5c94b --- /dev/null +++ b/src/osp/tasks/worker.h @@ -0,0 +1,42 @@ +/** + * Open Space Program + * Copyright © 2019-2023 Open Space Program Project + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#pragma once + +#include +#include + +namespace osp +{ + +enum class TaskAction +{ + Cancel = 1 << 0 + // CancelLoop = 1 << 1 +}; + +using TaskActions = Corrade::Containers::EnumSet; +CORRADE_ENUMSET_OPERATORS(TaskActions) + +} // namespace osp diff --git a/src/osp/types.h b/src/osp/types.h index 2dbbb8ea..bd011ffe 100644 --- a/src/osp/types.h +++ b/src/osp/types.h @@ -64,6 +64,7 @@ using Quaternion = Magnum::Math::Quaternion; using Quaterniond = Magnum::Math::Quaternion; using Rad = Magnum::Math::Rad; +using Radd = Magnum::Math::Rad; using Deg = Magnum::Math::Deg; } diff --git a/src/osp/universe/universe.h b/src/osp/universe/universe.h index f04591b8..d4c3c1d3 100644 --- a/src/osp/universe/universe.h +++ b/src/osp/universe/universe.h @@ -38,44 +38,39 @@ namespace osp::universe { +struct StrideDesc +{ + constexpr bool not_used() const noexcept { return m_stride == 0; } + + std::size_t m_offset{0}; + std::ptrdiff_t m_stride{0}; +}; + /** - * @brief Describes strided vector data within an externally stored buffer - * - * Intended to make different data layouts easier to access, and allows - * interleving different datatypes: - * * { XXXXX ... YYYYY ... ZZZZZ ... } - * * { XYZ XYZ XYZ XYZ XYZ XYZ ... } - * * { XYZ ??? XYZ ??? XYZ ??? ... } + * @brief Describes strided data within an externally stored buffer */ -template -struct SatVectorDesc +template +struct TypedStrideDesc : StrideDesc { using View_t = Corrade::Containers::StridedArrayView1D; using ViewConst_t = Corrade::Containers::StridedArrayView1D; using Data_t = Corrade::Containers::ArrayView; using DataConst_t = Corrade::Containers::ArrayView; - static constexpr std::size_t smc_dimensions = DIMENSIONS_T; - - // Offsets of xyz components in bytes - std::array m_offsets; - - // Assuming xyz all have the same stride - std::ptrdiff_t m_stride; - - constexpr View_t view(Data_t data, std::size_t count, std::size_t dimension) const + constexpr View_t view(Data_t data, std::size_t count) const noexcept { - auto first = reinterpret_cast((data.exceptPrefix(m_offsets[dimension])).data()); - return {data, first, count, m_stride}; + return stridedArrayView(data, reinterpret_cast(&data[m_offset]), count, m_stride); } - constexpr ViewConst_t view(DataConst_t data, std::size_t count, std::size_t dimension) const + constexpr ViewConst_t view(DataConst_t data, std::size_t count) const noexcept { - auto first = reinterpret_cast((data.exceptPrefix(m_offsets[dimension])).data()); - return {data, first, count, m_stride}; + return stridedArrayView(data, reinterpret_cast(&data[m_offset]), count, m_stride); } }; +template +using StrideDescArray_t = std::array, N>; + struct CoSpaceHierarchy { CoSpaceId m_parent{lgrn::id_null()}; @@ -97,35 +92,55 @@ struct CoSpaceSatData uint32_t m_satCount; uint32_t m_satCapacity; - Corrade::Containers::Array m_data; - SatVectorDesc m_satPositions; - SatVectorDesc m_satVelocities; - SatVectorDesc m_satRotations; + Corrade::Containers::Array m_data; + + // Describes m_data + StrideDescArray_t m_satPositions; + StrideDescArray_t m_satVelocities; + StrideDescArray_t m_satRotations; }; + struct CoSpaceCommon : CoSpaceTransform, CoSpaceHierarchy, CoSpaceSatData { }; -struct RedesignateSat +struct Universe +{ + Universe() = default; + Universe(Universe const&) = delete; + Universe(Universe&& move) = default; + + lgrn::IdRegistryStl m_coordIds; + + std::vector m_coordCommon; +}; + +struct SceneFrame : CoSpaceTransform, CoSpaceHierarchy { - SatId m_old; - SatId m_new; + Vector3g m_scenePosition; }; -struct TransferSat +template +constexpr void aux_partition(std::size_t const pos, TypedStrideDesc& rInterleveFirst, TypedStrideDesc& ... rInterleve) { - SatId m_satOld; - SatId m_satNew; + rInterleveFirst.m_offset = pos; - CoSpaceId m_coordOld; - CoSpaceId m_coordNew; -}; + if constexpr (sizeof...(T) != 0) + { + aux_partition(pos + sizeof(FIRST_T), rInterleve ...); + } +} -struct Universe +template +constexpr void partition(std::size_t& rPos, std::size_t count, TypedStrideDesc& ... rInterleve) { - lgrn::IdRegistryStl m_coordIds; + constexpr std::size_t stride = (sizeof(T) + ...); - std::vector m_coordCommon; -}; + (rInterleve.m_stride = ... = stride); + + aux_partition(rPos, rInterleve ...); + + rPos += stride * count; +} // INDEX_T is a template parameter to allow passing in "strong typedef" types, // like enum classes and having them converted without warning to size_t. @@ -141,28 +156,28 @@ constexpr VEC_T to_vec(INDEX_T i, RANGE_T&& ... args) noexcept /** * @brief Get StridedArrayView1Ds from all components of a SatVectorDesc * - * @param satVec [in] SatVectorDesc describing layout in rData - * @param rData [in] unsigned char* Satellite data, optionally const - * @param satCount [in] Range of valid satellites + * @param strideDescArray [in] Array of stride description for data in rData + * @param rData [in] unsigned char* Satellite data, optionally const + * @param satCount [in] Range of valid satellites * * @return std::array of views, intended to work with structured bindings */ template constexpr auto sat_views( - SatVectorDesc const& satVec, + StrideDescArray_t const& strideDescArray, DATA_T &rData, std::size_t satCount) noexcept { // Recursive call to make COUNT_T a range of numbers from 0 to DIMENSIONS_T - // This is unpacked into satVec.view(...) to access components + // This is unpacked into strideDescArray[COUNT_T].view(...) to access components constexpr int countUp = sizeof...(COUNT_T); if constexpr (countUp != DIMENSIONS_T) { - return sat_views(satVec, rData, satCount); + return sat_views(strideDescArray, rData, satCount); } else { - return std::array{ satVec.view(Corrade::Containers::arrayView(rData), satCount, COUNT_T) ... }; + return std::array{ strideDescArray[COUNT_T].view(Corrade::Containers::arrayView(rData), satCount) ... }; } } diff --git a/src/osp/universe/universetypes.h b/src/osp/universe/universetypes.h index 618c2796..4396610e 100644 --- a/src/osp/universe/universetypes.h +++ b/src/osp/universe/universetypes.h @@ -33,8 +33,8 @@ namespace osp::universe { -enum class SatId : uint32_t {}; -enum class CoSpaceId : uint32_t {}; +using SatId = uint32_t; +using CoSpaceId = uint32_t; } diff --git a/src/osp/unpack.h b/src/osp/unpack.h index f10e9e2d..26c7fb3a 100644 --- a/src/osp/unpack.h +++ b/src/osp/unpack.h @@ -45,6 +45,13 @@ constexpr auto& unpack(RANGE_T &rIn) return *reinterpret_cast(std::data(rIn)); } +template +constexpr auto& resize_then_unpack(CONTAINER_T &rIn) +{ + rIn.resize(N); + return unpack(rIn); +} + } diff --git a/src/test_application/ActiveApplication.cpp b/src/test_application/MagnumApplication.cpp similarity index 88% rename from src/test_application/ActiveApplication.cpp rename to src/test_application/MagnumApplication.cpp index 4f4706e9..f3988032 100644 --- a/src/test_application/ActiveApplication.cpp +++ b/src/test_application/MagnumApplication.cpp @@ -23,7 +23,7 @@ * SOFTWARE. */ -#include "ActiveApplication.h" +#include "MagnumApplication.h" #include "osp/types.h" #include @@ -34,8 +34,8 @@ using namespace testapp; -using Key_t = ActiveApplication::KeyEvent::Key; -using Mouse_t = ActiveApplication::MouseEvent::Button; +using Key_t = MagnumApplication::KeyEvent::Key; +using Mouse_t = MagnumApplication::MouseEvent::Button; using osp::input::sc_keyboard; using osp::input::sc_mouse; @@ -48,26 +48,29 @@ using osp::input::EVarOperator; using Magnum::Platform::Application; -ActiveApplication::ActiveApplication(const Application::Arguments& arguments, +MagnumApplication::MagnumApplication(const Application::Arguments& arguments, UserInputHandler& rUserInput) : Application{arguments, Configuration{}.setTitle("OSP-Magnum").setSize({1280, 720})} , m_rUserInput(rUserInput) { + // temporary fixed 60fps. No physics interpolation or anything is implemented yet + setSwapInterval(1); + setMinimalLoopPeriod(16); m_timeline.start(); } -ActiveApplication::~ActiveApplication() +MagnumApplication::~MagnumApplication() { - m_onDraw = {}; + m_ospApp.reset(nullptr); } -void ActiveApplication::drawEvent() +void MagnumApplication::drawEvent() { m_rUserInput.update_controls(); - if (m_onDraw.operator bool()) + if (m_ospApp != nullptr) { - m_onDraw(*this, m_timeline.previousFrameDuration()); + m_ospApp->draw(*this, m_timeline.previousFrameDuration()); } m_rUserInput.clear_events(); @@ -77,38 +80,38 @@ void ActiveApplication::drawEvent() redraw(); } -void ActiveApplication::keyPressEvent(KeyEvent& event) +void MagnumApplication::keyPressEvent(KeyEvent& event) { if (event.isRepeated()) { return; } m_rUserInput.event_raw(osp::input::sc_keyboard, (int) event.key(), osp::input::EButtonEvent::Pressed); } -void ActiveApplication::keyReleaseEvent(KeyEvent& event) +void MagnumApplication::keyReleaseEvent(KeyEvent& event) { if (event.isRepeated()) { return; } m_rUserInput.event_raw(osp::input::sc_keyboard, (int) event.key(), osp::input::EButtonEvent::Released); } -void ActiveApplication::mousePressEvent(MouseEvent& event) +void MagnumApplication::mousePressEvent(MouseEvent& event) { m_rUserInput.event_raw(osp::input::sc_mouse, (int) event.button(), osp::input::EButtonEvent::Pressed); } -void ActiveApplication::mouseReleaseEvent(MouseEvent& event) +void MagnumApplication::mouseReleaseEvent(MouseEvent& event) { m_rUserInput.event_raw(osp::input::sc_mouse, (int) event.button(), osp::input::EButtonEvent::Released); } -void ActiveApplication::mouseMoveEvent(MouseMoveEvent& event) +void MagnumApplication::mouseMoveEvent(MouseMoveEvent& event) { m_rUserInput.mouse_delta(event.relativePosition()); } -void ActiveApplication::mouseScrollEvent(MouseScrollEvent & event) +void MagnumApplication::mouseScrollEvent(MouseScrollEvent & event) { m_rUserInput.scroll_delta(static_cast(event.offset())); } @@ -218,9 +221,9 @@ const std::map> gc_buttonMap = { {"F12", {sc_keyboard, (int)Key_t::F12 }}, //Mouse - {"RMouse", {sc_mouse, (int)ActiveApplication::MouseEvent::Button::Right }}, - {"LMouse", {sc_mouse, (int)ActiveApplication::MouseEvent::Button::Left }}, - {"MMouse", {sc_mouse, (int)ActiveApplication::MouseEvent::Button::Middle }} + {"RMouse", {sc_mouse, (int)MagnumApplication::MouseEvent::Button::Right }}, + {"LMouse", {sc_mouse, (int)MagnumApplication::MouseEvent::Button::Left }}, + {"MMouse", {sc_mouse, (int)MagnumApplication::MouseEvent::Button::Middle }} }; ControlExprConfig_t parse_control(std::string_view str) noexcept diff --git a/src/test_application/ActiveApplication.h b/src/test_application/MagnumApplication.h similarity index 71% rename from src/test_application/ActiveApplication.h rename to src/test_application/MagnumApplication.h index 31a5402f..05adeb4e 100644 --- a/src/test_application/ActiveApplication.h +++ b/src/test_application/MagnumApplication.h @@ -34,33 +34,39 @@ #include // workaround: memcpy needed by SDL2 #include -#include #include namespace testapp { -class ActiveApplication; +class MagnumApplication; -// Stored inside an ActiveApplicaton to use as a main draw function. -// Renderer state can be stored in lambda capture -using on_draw_t = std::function; +class IOspApplication +{ +public: + virtual ~IOspApplication() = default; + + virtual void run(MagnumApplication& rApp) = 0; + virtual void draw(MagnumApplication& rApp, float delta) = 0; + virtual void exit(MagnumApplication& rApp) = 0; +}; /** * @brief An interactive Magnum application * * This is intended to run a flight scene, map view, vehicle editor, or menu. */ -class ActiveApplication : public Magnum::Platform::Application +class MagnumApplication : public Magnum::Platform::Application { - public: - explicit ActiveApplication( + using AppPtr_t = std::unique_ptr; + + explicit MagnumApplication( const Magnum::Platform::Application::Arguments& arguments, osp::input::UserInputHandler& rUserInput); - ~ActiveApplication(); + ~MagnumApplication(); void keyPressEvent(KeyEvent& event) override; void keyReleaseEvent(KeyEvent& event) override; @@ -70,16 +76,28 @@ class ActiveApplication : public Magnum::Platform::Application void mouseMoveEvent(MouseMoveEvent& event) override; void mouseScrollEvent(MouseScrollEvent& event) override; - void set_on_draw(on_draw_t onDraw) + void exec() { - m_onDraw = std::move(onDraw); + m_ospApp->run(*this); + Magnum::Platform::Application::exec(); + m_ospApp->exit(*this); + } + + void exit() + { + Magnum::Platform::Application::exit(); + } + + void set_osp_app(AppPtr_t ospApp) + { + m_ospApp = std::move(ospApp); } private: void drawEvent() override; - on_draw_t m_onDraw; + AppPtr_t m_ospApp; osp::input::UserInputHandler &m_rUserInput; @@ -92,11 +110,11 @@ void config_controls(osp::input::UserInputHandler& rUserInput); } /** -* Parses the control string from the config file. -* -* A "None" input returns a empty vector. -* -* @param Control string -* @returns vector of the control created from the string. -*/ + * Parses the control string from the config file. + * + * A "None" input returns a empty vector. + * + * @param Control string + * @returns vector of the control created from the string. + */ osp::input::ControlExprConfig_t parse_control(std::string_view str) noexcept; diff --git a/src/test_application/activescenes/identifiers.h b/src/test_application/activescenes/identifiers.h index 1aaced08..828457d9 100644 --- a/src/test_application/activescenes/identifiers.h +++ b/src/test_application/activescenes/identifiers.h @@ -24,73 +24,183 @@ */ #pragma once -// Identifiers made for OSP_ACQUIRE_* and OSP_UNPACK_* macros -// Used to declare variable names for TopDataIds and TagIds -// #define OSP_[DATA/TAGS]_NAME <# of identifiers>, a, b, c, d, ... +#include -// Tag naming: -// -// tg...Evt: Event, tasks with these tags are enqueued externally -// tg...New: Adds new instances -// tg...Del: Deletes instances -// tg...Mod: Modifies some data -// tg...Req: Requires some data after its modified -// tg...Prv: Requires previous from last update -// tg...Clr: Clears a queue after its used -// -// * Tasks with a tg...Req tag will run AFTER its corresponding tg...Mod tag -// * New often depends on Delete, as deleting instances first leaves empty -// spaces in a container, best immediately filled with new instances -// +#include -// Scene sessions +namespace testapp +{ -#define OSP_DATA_TESTAPP_COMMON_SCENE 8, \ - idDeltaTimeIn, idActiveIds, idBasic, idDrawing, idDrawingRes, idDelEnts, idDelTotal, idNMesh -#define OSP_TAGS_TESTAPP_COMMON_SCENE 34, \ - tgCleanupEvt, tgResyncEvt, tgSyncEvt, tgSceneEvt, tgTimeEvt, \ - tgEntDel, tgEntNew, tgEntReq, \ - tgDelEntMod, tgDelEntReq, tgDelEntClr, \ - tgDelTotalMod, tgDelTotalReq, tgDelTotalClr, \ - tgTransformMod, tgTransformDel, tgTransformNew, tgTransformReq, \ - tgHierMod, tgHierModEnd, tgHierDel, tgHierNew, tgHierReq, \ - tgDrawDel, tgDrawMod, tgDrawReq, \ - tgMeshDel, tgMeshMod, tgMeshReq, tgMeshClr, \ - tgTexDel, tgTexMod, tgTexReq, tgTexClr +enum class EStgOptn : uint8_t +{ + ModifyOrSignal, + Schedule, + Run, + Done +}; +OSP_DECLARE_STAGE_NAMES(EStgOptn, "Modify/Signal", "Schedule", "Run", "Done"); +OSP_DECLARE_STAGE_SCHEDULE(EStgOptn, EStgOptn::Schedule); -#define OSP_DATA_TESTAPP_MATERIAL 2, \ - idMatEnts, idMatDirty -#define OSP_TAGS_TESTAPP_MATERIAL 4, \ - tgMatDel, tgMatMod, tgMatReq, tgMatClr +enum class EStgEvnt : uint8_t +{ + Run_, + Done_ +}; +OSP_DECLARE_STAGE_NAMES(EStgEvnt, "Run", "Done"); +OSP_DECLARE_STAGE_NO_SCHEDULE(EStgEvnt); +/** + * @brief Intermediate container that is filled, used, then cleared right away + */ +enum class EStgIntr : uint8_t +{ + Resize, + Modify_, + Schedule_, + UseOrRun, + Clear +}; +OSP_DECLARE_STAGE_NAMES(EStgIntr, "Resize", "Modify", "Schedule", "Use/Run", "Clear"); +OSP_DECLARE_STAGE_SCHEDULE(EStgIntr, EStgIntr::Schedule_); + +/** + * @brief 'Reversed' Intermediate container + */ +enum class EStgRevd : uint8_t +{ + Schedule__, + UseOrRun_, + Clear_, + Resize_, + Modify__, +}; +OSP_DECLARE_STAGE_NAMES(EStgRevd, "Schedule", "Use/Run", "Clear", "Resize", "Modify"); +OSP_DECLARE_STAGE_SCHEDULE(EStgRevd, EStgRevd::Schedule__); + +/** + * @brief Continuous Containers, data that persists and is modified over time + * + */ +enum class EStgCont : uint8_t +{ + Prev, + ///< Previous state of container + + Delete, + ///< Remove elements from a container or mark them for deletion. This often involves reading + ///< a set of elements to delete. This is run first since it leaves empty spaces for new + ///< elements to fill directly after + + New, + ///< Add new elements. Potentially resize the container to fit more elements + + Modify, + ///< Modify existing elements + + Ready + ///< Container is ready to use +}; +OSP_DECLARE_STAGE_NAMES(EStgCont, "Prev", "Delete", "New", "Modify", "Use"); +OSP_DECLARE_STAGE_NO_SCHEDULE(EStgCont); + +enum class EStgFBO +{ + Bind, + Draw, + Unbind +}; +OSP_DECLARE_STAGE_NAMES(EStgFBO, "Bind", "Draw", "Unbind"); +OSP_DECLARE_STAGE_NO_SCHEDULE(EStgFBO); + +//----------------------------------------------------------------------------- + +inline void register_stage_enums() +{ + osp::PipelineInfo::sm_stageNames.resize(32); + osp::PipelineInfo::register_stage_enum(); + osp::PipelineInfo::register_stage_enum(); + osp::PipelineInfo::register_stage_enum(); + osp::PipelineInfo::register_stage_enum(); + osp::PipelineInfo::register_stage_enum(); + osp::PipelineInfo::register_stage_enum(); +} + +using osp::PipelineDef; + +//----------------------------------------------------------------------------- + +#define TESTAPP_DATA_APPLICATION 2, \ + idResources, idMainLoopCtrl +struct PlApplication +{ + PipelineDef mainLoop {"mainLoop"}; +}; + +//----------------------------------------------------------------------------- + +#define TESTAPP_DATA_SCENE 1, \ + idDeltaTimeIn +struct PlScene +{ + PipelineDef cleanup {"cleanup - Scene cleanup before destruction"}; + PipelineDef update {"update"}; +}; + +#define TESTAPP_DATA_COMMON_SCENE 6, \ + idBasic, idDrawing, idDrawingRes, idActiveEntDel, idDrawEntDel, idNMesh +struct PlCommonScene +{ + PipelineDef activeEnt {"activeEnt - ActiveEnt ID Registry"}; + PipelineDef activeEntResized {"activeEntResized"}; + PipelineDef activeEntDelete {"activeEntDelete - Vector of ActiveEnts that need to be deleted"}; + + PipelineDef transform {"transform"}; + PipelineDef hierarchy {"hierarchy"}; + + PipelineDef drawEnt {"drawEnt"}; + PipelineDef drawEntResized {"drawEntResized"}; + PipelineDef drawEntDelete {"drawEntDelete - Vector of DrawEnts that need to be deleted"}; + PipelineDef mesh {"mesh"}; + PipelineDef texture {"texture"}; -#define OSP_DATA_TESTAPP_MATERIAL 2, \ - idMatEnts, idMatDirty -#define OSP_TAGS_TESTAPP_MATERIAL 4, \ - tgMatDel, tgMatMod, tgMatReq, tgMatClr + PipelineDef entMesh {"entMesh - Scene-side DrawEnt-MeshId association"}; + PipelineDef entTexture {"entTexture - Scene-side DrawEnt-TexId association"}; + PipelineDef entTextureDirty {"entTextureDirty"}; + PipelineDef entMeshDirty {"entMeshDirty"}; + PipelineDef meshResDirty {"meshResDirty"}; + PipelineDef textureResDirty {"textureResDirty"}; -#define OSP_DATA_TESTAPP_PHYSICS 3, \ + PipelineDef material {"material"}; + PipelineDef materialDirty {"materialDirty"}; +}; + + +#define TESTAPP_DATA_PHYSICS 3, \ idPhys, idHierBody, idPhysIn -#define OSP_TAGS_TESTAPP_PHYSICS 6, \ - tgPhysPrv, tgPhysDel, tgPhysMod, tgPhysReq, \ - tgPhysTransformMod, tgPhysTransformReq +struct PlPhysics +{ + PipelineDef physBody {"physBody"}; + PipelineDef physUpdate {"physUpdate"}; +}; -#define OSP_DATA_TESTAPP_SHAPE_SPAWN 2, \ - idSpawner, idSpawnerEnts -#define OSP_TAGS_TESTAPP_SHAPE_SPAWN 5, \ - tgSpawnMod, tgSpawnReq, tgSpawnClr, \ - tgSpawnEntMod, tgSpawnEntReq +#define TESTAPP_DATA_SHAPE_SPAWN 1, \ + idSpawner +struct PlShapeSpawn +{ + PipelineDef spawnRequest {"spawnRequest"}; + PipelineDef spawnedEnts {"spawnedEnts"}; +}; -#define OSP_DATA_TESTAPP_PREFABS 1, \ +#define TESTAPP_DATA_PREFABS 1, \ idPrefabInit #define OSP_TAGS_TESTAPP_PREFABS 7, \ tgPrefabMod, tgPrefabReq, tgPrefabClr, \ @@ -99,15 +209,17 @@ -#define OSP_DATA_TESTAPP_BOUNDS 2, \ +#define TESTAPP_DATA_BOUNDS 2, \ idBounds, idOutOfBounds -#define OSP_TAGS_TESTAPP_BOUNDS 5, \ - tgBoundsSetDel, tgBoundsSetMod, tgBoundsSetReq, \ - tgOutOfBoundsPrv, tgOutOfBoundsMod +struct PlBounds +{ + PipelineDef boundsSet {"boundsSet"}; + PipelineDef outOfBounds {"outOfBounds"}; +}; -#define OSP_DATA_TESTAPP_PARTS 6, \ +#define TESTAPP_DATA_PARTS 6, \ idScnParts, idPartInit, idUpdMach, idMachEvtTags, idMachUpdEnqueue, idtgNodeUpdEvt #define OSP_TAGS_TESTAPP_PARTS 17, \ tgPartMod, tgPartReq, tgPartClr, \ @@ -120,7 +232,7 @@ -#define OSP_DATA_TESTAPP_VEHICLE_SPAWN 1, \ +#define TESTAPP_DATA_VEHICLE_SPAWN 1, \ idVehicleSpawn #define OSP_TAGS_TESTAPP_VEHICLE_SPAWN 11, \ tgVsBasicInMod, tgVsBasicInReq, tgVsBasicInClr, \ @@ -131,7 +243,7 @@ -#define OSP_DATA_TESTAPP_VEHICLE_SPAWN_VB 1, \ +#define TESTAPP_DATA_VEHICLE_SPAWN_VB 1, \ idVehicleSpawnVB #define OSP_TAGS_TESTAPP_VEHICLE_SPAWN_VB 10, \ tgVbSpBasicInMod, tgVbSpBasicInReq, \ @@ -141,12 +253,12 @@ tgVbNodeMod, tgVbNodeReq -#define OSP_DATA_TESTAPP_TEST_VEHICLES 1, \ +#define TESTAPP_DATA_TEST_VEHICLES 1, \ idTVPartVehicle -#define OSP_DATA_TESTAPP_SIGNALS_FLOAT 2, \ +#define TESTAPP_DATA_SIGNALS_FLOAT 2, \ idSigValFloat, idSigUpdFloat #define OSP_TAGS_TESTAPP_SIGNALS_FLOAT 5, \ tgSigFloatLinkMod, tgSigFloatLinkReq, \ @@ -154,7 +266,7 @@ -#define OSP_DATA_TESTAPP_MACH_ROCKET 1, \ +#define TESTAPP_DATA_MACH_ROCKET 1, \ idDummy #define OSP_TAGS_TESTAPP_MACH_ROCKET 1, \ tgMhRocketEvt @@ -162,18 +274,19 @@ #define OSP_TAGS_TESTAPP_MACH_RCSDRIVER 1, \ tgMhRcsDriverEvt -#define OSP_DATA_TESTAPP_NEWTON 1, \ +#define TESTAPP_DATA_NEWTON 1, \ idNwt -#define OSP_TAGS_TESTAPP_NEWTON 5, \ - tgNwtBodyPrv, tgNwtBodyDel, tgNwtBodyMod, tgNwtBodyReq, tgNwtBodyClr - +struct PlNewton +{ + PipelineDef nwtBody {"nwtBody"}; +}; -#define OSP_DATA_TESTAPP_NEWTON_FORCES 1, \ +#define TESTAPP_DATA_NEWTON_FORCES 1, \ idNwtFactors -#define OSP_DATA_TESTAPP_NEWTON_ACCEL 1, \ +#define TESTAPP_DATA_NEWTON_ACCEL 1, \ idAcceleration @@ -184,58 +297,108 @@ -#define OSP_DATA_TESTAPP_ROCKETS_NWT 1, \ +#define TESTAPP_DATA_ROCKETS_NWT 1, \ idRocketsNwt +//----------------------------------------------------------------------------- + +// Universe sessions + +#define TESTAPP_DATA_UNI_CORE 2, \ + idUniverse, tgUniDeltaTimeIn +#define OSP_TAGS_TESTAPP_UNI_CORE 4, \ + tgUniUpdEvt, tgUniTimeEvt, \ + tgUniTransferMod, tgUniTransferReq + +#define TESTAPP_DATA_UNI_SCENEFRAME 1, \ + idScnFrame +#define OSP_TAGS_TESTAPP_UNI_SCENEFRAME 2, \ + tgScnFramePosMod, tgScnFramePosReq + +#define TESTAPP_DATA_UNI_PLANETS 2, \ + idPlanetMainSpace, idSatSurfaceSpaces //----------------------------------------------------------------------------- // Renderer sessions, tend to exist only when the window is open -#define OSP_DATA_TESTAPP_APP 1, \ +#define TESTAPP_DATA_WINDOW_APP 1, \ idUserInput -#define OSP_TAGS_TESTAPP_APP 2, \ - tgRenderEvt, tgInputEvt +struct PlWindowApp +{ + PipelineDef inputs {"inputs"}; +}; + + + +#define TESTAPP_DATA_MAGNUM 2, \ + idActiveApp, idRenderGl +struct PlMagnum +{ + PipelineDef cleanup {"cleanup - Cleanup magnum renderer resources before destruction"}; + PipelineDef sync {"sync"}; + PipelineDef resync {"resync"}; + PipelineDef meshGL {"meshGL"}; + PipelineDef textureGL {"textureGL"}; -#define OSP_DATA_TESTAPP_APP_MAGNUM 3, \ - idUserInput, idActiveApp, idRenderGl -#define OSP_TAGS_TESTAPP_APP_MAGNUM 4, \ - tgRenderEvt, tgInputEvt, tgGlUse, tgCleanupMagnumEvt + PipelineDef entMeshGL {"entMeshGL"}; + PipelineDef entTextureGL {"entTextureGL"}; +}; -#define OSP_DATA_TESTAPP_COMMON_RENDERER 3, \ +#define TESTAPP_DATA_COMMON_RENDERER 3, \ idScnRender, idGroupFwd, idCamera -#define OSP_TAGS_TESTAPP_COMMON_RENDERER 20, \ - tgDrawGlDel, tgDrawGlMod, tgDrawGlReq, \ - tgMeshGlMod, tgMeshGlReq, \ - tgTexGlMod, tgTexGlReq, \ - tgEntTexMod, tgEntTexReq, \ - tgEntMeshMod, tgEntMeshReq, \ - tgCameraMod, tgCameraReq, \ - tgGroupFwdDel, tgGroupFwdMod, tgGroupFwdReq, \ - tgDrawTransformDel, tgDrawTransformNew, tgDrawTransformMod, tgDrawTransformReq +struct PlSceneRenderer +{ + PipelineDef render {"render"}; + PipelineDef fbo {"fboRender"}; + PipelineDef scnRender {"scnRender"}; + PipelineDef group {"group"}; + PipelineDef groupEnts {"groupEnts"}; + PipelineDef drawTransforms {"drawTransforms"}; + PipelineDef camera {"camera"}; + PipelineDef entMesh {"entMesh"}; + PipelineDef entTexture {"entTexture"}; +}; -#define OSP_DATA_TESTAPP_CAMERA_CTRL 1, \ + +#define TESTAPP_DATA_CAMERA_CTRL 1, \ idCamCtrl -#define OSP_TAGS_TESTAPP_CAMERA_CTRL 2, \ - tgCamCtrlMod, tgCamCtrlReq +struct PlCameraCtrl +{ + PipelineDef camCtrl {"camCtrl"}; +}; + + +#define TESTAPP_DATA_SHADER_VISUALIZER 1, \ + idDrawShVisual + + +#define TESTAPP_DATA_SHADER_PHONG 1, \ + idDrawShPhong -#define OSP_DATA_TESTAPP_SHADER_VISUALIZER 1, \ - idDrawVisual -#define OSP_TAGS_TESTAPP_SHADER_VISUALIZER 3, \ - tgRenderEvt, tgInputEvt, tgGlUse +#define TESTAPP_DATA_SHADER_FLAT 1, \ + idDrawShFlat -#define OSP_DATA_TESTAPP_VEHICLE_CONTROL 1, \ + +#define TESTAPP_DATA_INDICATOR 1, \ + idIndicator + + + +#define TESTAPP_DATA_VEHICLE_CONTROL 1, \ idVhControls #define OSP_TAGS_TESTAPP_VEHICLE_CONTROL 2, \ tgSelUsrCtrlMod, tgSelUsrCtrlReq + +} // namespace testapp diff --git a/src/test_application/activescenes/scenarios.cpp b/src/test_application/activescenes/scenarios.cpp index d846ea50..d85bf2b8 100644 --- a/src/test_application/activescenes/scenarios.cpp +++ b/src/test_application/activescenes/scenarios.cpp @@ -32,9 +32,10 @@ #include "scene_misc.h" #include "scene_newton.h" #include "scene_renderer.h" +#include "scene_universe.h" #include "scene_vehicles.h" -#include "../ActiveApplication.h" +#include "../MagnumApplication.h" #include "../VehicleBuilder.h" #include @@ -42,6 +43,8 @@ #include #include +#include + #include #include @@ -53,135 +56,217 @@ using namespace osp; namespace testapp { -static void setup_magnum_draw(MainView mainView, Session const& magnum, Session const& scnCommon, Session const& scnRender) +static constexpr auto sc_matVisualizer = active::MaterialId(0); +static constexpr auto sc_matFlat = active::MaterialId(1); +static constexpr auto sc_matPhong = active::MaterialId(2); +static constexpr int sc_materialCount = 4; + +struct CommonMagnumApp : IOspApplication { - OSP_SESSION_UNPACK_DATA(scnRender, TESTAPP_COMMON_RENDERER); - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_DATA(magnum, TESTAPP_APP_MAGNUM); - OSP_SESSION_UNPACK_TAGS(magnum, TESTAPP_APP_MAGNUM); + CommonMagnumApp(TestApp &rTestApp, MainLoopControl &rMainLoopCtrl, PipelineId mainLoop, PipelineId inputs, PipelineId renderSync, PipelineId sceneUpdate, PipelineId sceneRender) noexcept + : m_rTestApp { rTestApp } + , m_rMainLoopCtrl { rMainLoopCtrl } + , m_mainLoop { mainLoop } + , m_inputs { inputs } + , m_renderSync { renderSync } + , m_sceneUpdate { sceneUpdate } + , m_sceneRender { sceneRender } + + { } + + void run(MagnumApplication& rApp) override + { + // Start the main loop - Tags &rTags = mainView.m_rTags; - Tasks &rTasks = mainView.m_rTasks; - TopTaskDataVec_t &rTaskData = mainView.m_rTaskData; - ExecutionContext &rExec = mainView.m_rExec; - ArrayView topData = mainView.m_topData; + PipelineId const mainLoop = m_rTestApp.m_application.get_pipelines().mainLoop; + m_rTestApp.m_pExecutor->run(m_rTestApp, mainLoop); + } - auto &rActiveApp = top_get(topData, idActiveApp); + void draw(MagnumApplication& rApp, float delta) override + { + // Magnum Application's main loop is here - auto &rCamera = top_get(topData, idCamera); - rCamera.set_aspect_ratio(Vector2{Magnum::GL::defaultFramebuffer.viewport().size()}); + m_rMainLoopCtrl = MainLoopControl{ + .doUpdate = true, + .doSync = true, + .doResync = m_justStarting, + .doRender = true, + }; + m_justStarting = false; - top_enqueue_quick(rTags, rTasks, rExec, {tgSyncEvt, tgResyncEvt}); - top_run_blocking(rTags, rTasks, rTaskData, topData, rExec); + m_rTestApp.m_pExecutor->signal(m_rTestApp, m_mainLoop); + m_rTestApp.m_pExecutor->signal(m_rTestApp, m_inputs); + m_rTestApp.m_pExecutor->signal(m_rTestApp, m_sceneUpdate); + m_rTestApp.m_pExecutor->signal(m_rTestApp, m_sceneRender); + m_rTestApp.m_pExecutor->signal(m_rTestApp, m_renderSync); - auto const runTags = {tgSyncEvt, tgSceneEvt, tgTimeEvt, tgRenderEvt, tgInputEvt}; + m_rTestApp.m_pExecutor->wait(m_rTestApp); + } - rActiveApp.set_on_draw( [&rTags, &rTasks, &rExec, &rTaskData, topData, runTagsVec = std::vector(runTags)] - (ActiveApplication& rApp, float delta) + void exit(MagnumApplication& rApp) override { - // Magnum Application's main loop is here + m_rMainLoopCtrl = MainLoopControl{ + .doUpdate = false, + .doSync = false, + .doResync = false, + .doRender = false, + }; - top_enqueue_quick(rTags, rTasks, rExec, runTagsVec); - top_run_blocking(rTags, rTasks, rTaskData, topData, rExec); + m_rTestApp.m_pExecutor->signal(m_rTestApp, m_mainLoop); + m_rTestApp.m_pExecutor->signal(m_rTestApp, m_inputs); + m_rTestApp.m_pExecutor->signal(m_rTestApp, m_sceneUpdate); + m_rTestApp.m_pExecutor->signal(m_rTestApp, m_sceneRender); + m_rTestApp.m_pExecutor->signal(m_rTestApp, m_renderSync); - // Enqueued tasks that don't run indicate a deadlock - if ( ! std::all_of(std::begin(rExec.m_taskQueuedCounts), - std::end(rExec.m_taskQueuedCounts), - [] (unsigned int n) { return n == 0; })) + m_rTestApp.m_pExecutor->wait(m_rTestApp); + + if (m_rTestApp.m_pExecutor->is_running(m_rTestApp)) { - OSP_LOG_ERROR("Deadlock detected!"); - debug_top_print_deadlock(rTags, rTasks, rTaskData, rExec); + // Main loop must have stopped, but didn't! + m_rTestApp.m_pExecutor->wait(m_rTestApp); std::abort(); } - }); + } + + TestApp &m_rTestApp; + MainLoopControl &m_rMainLoopCtrl; + + PipelineId m_mainLoop; + PipelineId m_inputs; + PipelineId m_renderSync; + PipelineId m_sceneUpdate; + PipelineId m_sceneRender; + + bool m_justStarting{true}; +}; + +static void setup_magnum_draw(TestApp& rTestApp, Session const& scene, Session const& scnRenderer, std::vector run = {}) +{ + OSP_DECLARE_GET_DATA_IDS(scnRenderer, TESTAPP_DATA_COMMON_RENDERER); + OSP_DECLARE_GET_DATA_IDS(rTestApp.m_application, TESTAPP_DATA_APPLICATION); + OSP_DECLARE_GET_DATA_IDS(rTestApp.m_magnum, TESTAPP_DATA_MAGNUM); + + auto &rMainLoopCtrl = top_get (rTestApp.m_topData, idMainLoopCtrl); + auto &rActiveApp = top_get(rTestApp.m_topData, idActiveApp); + auto &rCamera = top_get (rTestApp.m_topData, idCamera); + + rCamera.set_aspect_ratio(Vector2{Magnum::GL::defaultFramebuffer.viewport().size()}); + + PipelineId const mainLoop = rTestApp.m_application .get_pipelines() .mainLoop; + PipelineId const inputs = rTestApp.m_windowApp .get_pipelines() .inputs; + PipelineId const renderSync = rTestApp.m_magnum .get_pipelines() .sync; + PipelineId const sceneUpdate = scene .get_pipelines() .update; + PipelineId const sceneRender = scnRenderer .get_pipelines() .render; + + rActiveApp.set_osp_app( std::make_unique(rTestApp, rMainLoopCtrl, mainLoop, inputs, renderSync, sceneUpdate, sceneRender) ); } static ScenarioMap_t make_scenarios() -{ +{ ScenarioMap_t scenarioMap; - auto const add_scenario = [&scenarioMap] (std::string_view name, std::string_view desc, SceneSetup_t run) + register_stage_enums(); + + auto const add_scenario = [&scenarioMap] (std::string_view name, std::string_view desc, SceneSetupFunc_t run) { scenarioMap.emplace(name, ScenarioOption{desc, run}); }; add_scenario("enginetest", "Basic game engine and drawing scenario (without using TopTasks)", - [] (MainView mainView, PkgId pkg, Sessions_t& sceneOut) -> RendererSetup_t + [] (TestApp& rTestApp) -> RendererSetupFunc_t { - sceneOut.resize(1); - TopDataId const idSceneData = sceneOut.front().acquire_data<1>(mainView.m_topData).front(); - auto &rResources = top_get(mainView.m_topData, mainView.m_idResources); + SessionGroup &rOut = rTestApp.m_scene; + rOut.m_sessions.resize(1); + TopDataId const idSceneData = rOut.m_sessions[0].acquire_data<1>(rTestApp.m_topData)[0]; + + OSP_DECLARE_GET_DATA_IDS(rTestApp.m_application, TESTAPP_DATA_APPLICATION); + + auto &rResources = top_get(rTestApp.m_topData, idResources); // enginetest::setup_scene returns an entt::any containing one big // struct containing all the scene data. - top_assign(mainView.m_topData, idSceneData, enginetest::setup_scene(rResources, pkg)); + top_assign(rTestApp.m_topData, idSceneData, enginetest::setup_scene(rResources, rTestApp.m_defaultPkg)); - return [] (MainView mainView, Session const& magnum, Sessions_t const& scene, [[maybe_unused]] Sessions_t& rendererOut) + return [] (TestApp& rTestApp) { - TopDataId const idSceneData = scene.front().m_dataIds.front(); - auto& rScene = top_get(mainView.m_topData, idSceneData); + TopDataId const idSceneData = rTestApp.m_scene.m_sessions[0].m_data[0]; + auto &rScene = top_get(rTestApp.m_topData, idSceneData); - OSP_SESSION_UNPACK_DATA(magnum, TESTAPP_APP_MAGNUM); - auto &rActiveApp = top_get< ActiveApplication > (mainView.m_topData, idActiveApp); - auto &rRenderGl = top_get< active::RenderGL > (mainView.m_topData, idRenderGl); - auto &rUserInput = top_get< input::UserInputHandler >(mainView.m_topData, idUserInput); + OSP_DECLARE_GET_DATA_IDS(rTestApp.m_magnum, TESTAPP_DATA_MAGNUM); + OSP_DECLARE_GET_DATA_IDS(rTestApp.m_windowApp, TESTAPP_DATA_WINDOW_APP); + auto &rActiveApp = top_get< MagnumApplication > (rTestApp.m_topData, idActiveApp); + auto &rRenderGl = top_get< active::RenderGL > (rTestApp.m_topData, idRenderGl); + auto &rUserInput = top_get< input::UserInputHandler >(rTestApp.m_topData, idUserInput); // Renderer state is stored as lambda capture - rActiveApp.set_on_draw(enginetest::generate_draw_func(rScene, rActiveApp, rRenderGl, rUserInput)); + rActiveApp.set_osp_app(enginetest::generate_draw_func(rScene, rActiveApp, rRenderGl, rUserInput)); }; }); add_scenario("physics", "Newton Dynamics integration test scenario", - [] (MainView mainView, PkgId pkg, Sessions_t& sceneOut) -> RendererSetup_t + [] (TestApp& rTestApp) -> RendererSetupFunc_t { using namespace testapp::scenes; - auto const idResources = mainView.m_idResources; - auto &rTopData = mainView.m_topData; - auto &rTags = mainView.m_rTags; - Builder_t builder{rTags, mainView.m_rTasks, mainView.m_rTaskData}; + 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}; - sceneOut.resize(10); - auto & [scnCommon, matVisual, physics, shapeSpawn, droppers, bounds, newton, nwtGravSet, nwtGrav, shapeSpawnNwt] = unpack<10>(sceneOut); + auto & [ + scene, commonScene, physics, shapeSpawn, droppers, bounds, newton, nwtGravSet, nwtGrav, shapeSpawnNwt + ] = resize_then_unpack<10>(rTestApp.m_scene.m_sessions); // Compose together lots of Sessions - scnCommon = setup_common_scene (builder, rTopData, rTags, idResources, pkg); - matVisual = setup_material (builder, rTopData, rTags, scnCommon); - physics = setup_physics (builder, rTopData, rTags, scnCommon); - shapeSpawn = setup_shape_spawn (builder, rTopData, rTags, scnCommon, physics, matVisual); - droppers = setup_droppers (builder, rTopData, rTags, scnCommon, shapeSpawn); - bounds = setup_bounds (builder, rTopData, rTags, scnCommon, physics, shapeSpawn); - - newton = setup_newton (builder, rTopData, rTags, scnCommon, physics); - nwtGravSet = setup_newton_factors (builder, rTopData, rTags); - nwtGrav = setup_newton_force_accel (builder, rTopData, rTags, newton, nwtGravSet, Vector3{0.0f, 0.0f, -9.81f}); - shapeSpawnNwt = setup_shape_spawn_newton (builder, rTopData, rTags, scnCommon, physics, shapeSpawn, newton, nwtGravSet); + scene = setup_scene (builder, rTopData, application); + commonScene = setup_common_scene (builder, rTopData, scene, application, defaultPkg); + physics = setup_physics (builder, rTopData, scene, commonScene); + shapeSpawn = setup_shape_spawn (builder, rTopData, scene, commonScene, physics, sc_matVisualizer); + droppers = setup_droppers (builder, rTopData, scene, commonScene, shapeSpawn); + bounds = setup_bounds (builder, rTopData, scene, commonScene, shapeSpawn); + + newton = setup_newton (builder, rTopData, scene, commonScene, physics); + nwtGravSet = setup_newton_factors (builder, rTopData); + nwtGrav = setup_newton_force_accel (builder, rTopData, newton, nwtGravSet, Vector3{0.0f, 0.0f, -9.81f}); + shapeSpawnNwt = setup_shape_spawn_newton (builder, rTopData, commonScene, physics, shapeSpawn, newton, nwtGravSet); + + create_materials(rTopData, commonScene, sc_materialCount); + add_floor(rTopData, application, commonScene, shapeSpawn, sc_matVisualizer, defaultPkg); + + return [] (TestApp& rTestApp) + { + auto const application = rTestApp.m_application; + auto const windowApp = rTestApp.m_windowApp; + auto const magnum = rTestApp.m_magnum; + auto const defaultPkg = rTestApp.m_defaultPkg; + auto & rTopData = rTestApp.m_topData; - add_floor(rTopData, scnCommon, matVisual, shapeSpawn, idResources, pkg); + TopTaskBuilder builder{rTestApp.m_tasks, rTestApp.m_renderer.m_edges, rTestApp.m_taskData}; - return [] (MainView mainView, Session const& magnum, Sessions_t const& scene, [[maybe_unused]] Sessions_t& rendererOut) - { - auto &rTopData = mainView.m_topData; - auto &rTags = mainView.m_rTags; - Builder_t builder{mainView.m_rTags, mainView.m_rTasks, mainView.m_rTaskData}; + auto & [ + scene, commonScene, physics, shapeSpawn, droppers, bounds, newton, nwtGravSet, nwtGrav, shapeSpawnNwt + ] = unpack<10>(rTestApp.m_scene.m_sessions); - auto const& [scnCommon, matVisual, physics, shapeSpawn, droppers, bounds, newton, nwtGravSet, nwtGrav, shapeSpawnNwt] = unpack<10>(scene); + auto & [ + scnRender, cameraCtrl, cameraFree, shVisual, camThrow + ] = resize_then_unpack<5>(rTestApp.m_renderer.m_sessions); - rendererOut.resize(5); - auto & [scnRender, cameraCtrl, cameraFree, shVisual, camThrow] = unpack<5>(rendererOut); - scnRender = setup_scene_renderer (builder, rTopData, rTags, magnum, scnCommon, mainView.m_idResources); - cameraCtrl = setup_camera_ctrl (builder, rTopData, rTags, magnum, scnRender); - cameraFree = setup_camera_free (builder, rTopData, rTags, magnum, scnCommon, cameraCtrl); - shVisual = setup_shader_visualizer (builder, rTopData, rTags, magnum, scnCommon, scnRender, matVisual); - camThrow = setup_thrower (builder, rTopData, rTags, magnum, scnRender, cameraCtrl, shapeSpawn); + scnRender = setup_scene_renderer (builder, rTopData, application, windowApp, magnum, scene, commonScene); + cameraCtrl = setup_camera_ctrl (builder, rTopData, windowApp, scnRender); + cameraFree = setup_camera_free (builder, rTopData, windowApp, scene, cameraCtrl); + shVisual = setup_shader_visualizer (builder, rTopData, magnum, scene, commonScene, scnRender, sc_matVisualizer); + camThrow = setup_thrower (builder, rTopData, windowApp, cameraCtrl, shapeSpawn); - setup_magnum_draw(mainView, magnum, scnCommon, scnRender); + setup_magnum_draw(rTestApp, scene, scnRender); }; }); +#if 0 + add_scenario("vehicles", "Physics scenario but with Vehicles", - [] (MainView mainView, PkgId pkg, Sessions_t& sceneOut) -> RendererSetup_t + [] (MainView mainView, Sessions_t& sceneOut) -> RendererSetup_t { using namespace testapp::scenes; using namespace osp::active; @@ -191,46 +276,45 @@ static ScenarioMap_t make_scenarios() auto &rTags = mainView.m_rTags; Builder_t builder{rTags, mainView.m_rTasks, mainView.m_rTaskData}; - sceneOut.resize(24); auto & [ - scnCommon, matVisual, physics, shapeSpawn, + commonScene, matVisual, physics, shapeSpawn, prefabs, parts, vehicleSpawn, vehicleSpawnVB, vehicleSpawnRgd, signalsFloat, machRocket, machRcsDriver, testVehicles, droppers, gravity, bounds, thrower, newton, nwtGravSet, nwtGrav, shapeSpawnNwt, vehicleSpawnNwt, nwtRocketSet, rocketsNwt - ] = unpack<24>(sceneOut); - - scnCommon = setup_common_scene (builder, rTopData, rTags, idResources, pkg); - matVisual = setup_material (builder, rTopData, rTags, scnCommon); - physics = setup_physics (builder, rTopData, rTags, scnCommon); - shapeSpawn = setup_shape_spawn (builder, rTopData, rTags, scnCommon, physics, matVisual); - prefabs = setup_prefabs (builder, rTopData, rTags, scnCommon, physics, matVisual, idResources); - parts = setup_parts (builder, rTopData, rTags, scnCommon, idResources); - signalsFloat = setup_signals_float (builder, rTopData, rTags, scnCommon, parts); - vehicleSpawn = setup_vehicle_spawn (builder, rTopData, rTags, scnCommon); - vehicleSpawnVB = setup_vehicle_spawn_vb (builder, rTopData, rTags, scnCommon, prefabs, parts, vehicleSpawn, signalsFloat, idResources); - machRocket = setup_mach_rocket (builder, rTopData, rTags, scnCommon, parts, signalsFloat); - machRcsDriver = setup_mach_rcsdriver (builder, rTopData, rTags, scnCommon, parts, signalsFloat); - testVehicles = setup_test_vehicles (builder, rTopData, rTags, scnCommon, idResources); - droppers = setup_droppers (builder, rTopData, rTags, scnCommon, shapeSpawn); - bounds = setup_bounds (builder, rTopData, rTags, scnCommon, physics, shapeSpawn); - - newton = setup_newton (builder, rTopData, rTags, scnCommon, physics); + ] = resize_then_unpack<24>(sceneOut); + + commonScene = setup_common_scene (builder, rTopData, rTags, idResources, mainView.m_defaultPkg); + matVisual = setup_material (builder, rTopData, rTags, commonScene); + physics = setup_physics (builder, rTopData, rTags, commonScene); + shapeSpawn = setup_shape_spawn (builder, rTopData, rTags, commonScene, physics, matVisual); + prefabs = setup_prefabs (builder, rTopData, rTags, commonScene, physics, matVisual, idResources); + parts = setup_parts (builder, rTopData, rTags, commonScene, idResources); + signalsFloat = setup_signals_float (builder, rTopData, rTags, commonScene, parts); + vehicleSpawn = setup_vehicle_spawn (builder, rTopData, rTags, commonScene); + vehicleSpawnVB = setup_vehicle_spawn_vb (builder, rTopData, rTags, commonScene, prefabs, parts, vehicleSpawn, signalsFloat, idResources); + machRocket = setup_mach_rocket (builder, rTopData, rTags, commonScene, parts, signalsFloat); + machRcsDriver = setup_mach_rcsdriver (builder, rTopData, rTags, commonScene, parts, signalsFloat); + testVehicles = setup_test_vehicles (builder, rTopData, rTags, commonScene, idResources); + droppers = setup_droppers (builder, rTopData, rTags, commonScene, shapeSpawn); + bounds = setup_bounds (builder, rTopData, rTags, commonScene, physics, shapeSpawn); + + newton = setup_newton (builder, rTopData, rTags, commonScene, physics); nwtGravSet = setup_newton_factors (builder, rTopData, rTags); nwtGrav = setup_newton_force_accel (builder, rTopData, rTags, newton, nwtGravSet, Vector3{0.0f, 0.0f, -9.81f}); - shapeSpawnNwt = setup_shape_spawn_newton (builder, rTopData, rTags, scnCommon, physics, shapeSpawn, newton, nwtGravSet); - vehicleSpawnNwt = setup_vehicle_spawn_newton(builder, rTopData, rTags, scnCommon, physics, prefabs, parts, vehicleSpawn, newton, idResources); + shapeSpawnNwt = setup_shape_spawn_newton (builder, rTopData, rTags, commonScene, physics, shapeSpawn, newton, nwtGravSet); + vehicleSpawnNwt = setup_vehicle_spawn_newton(builder, rTopData, rTags, commonScene, physics, prefabs, parts, vehicleSpawn, newton, idResources); nwtRocketSet = setup_newton_factors (builder, rTopData, rTags); - rocketsNwt = setup_rocket_thrust_newton(builder, rTopData, rTags, scnCommon, physics, prefabs, parts, signalsFloat, newton, nwtRocketSet); + rocketsNwt = setup_rocket_thrust_newton(builder, rTopData, rTags, commonScene, physics, prefabs, parts, signalsFloat, newton, nwtRocketSet); - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(commonScene, TESTAPP_COMMON_SCENE); OSP_SESSION_UNPACK_DATA(vehicleSpawn, TESTAPP_VEHICLE_SPAWN); OSP_SESSION_UNPACK_DATA(vehicleSpawnVB, TESTAPP_VEHICLE_SPAWN_VB); OSP_SESSION_UNPACK_DATA(testVehicles, TESTAPP_TEST_VEHICLES); - add_floor(rTopData, scnCommon, matVisual, shapeSpawn, idResources, pkg); + add_floor(rTopData, commonScene, matVisual, shapeSpawn, idResources, mainView.m_defaultPkg); auto &rActiveIds = top_get (rTopData, idActiveIds); auto &rTVPartVehicle = top_get (rTopData, idTVPartVehicle); @@ -256,7 +340,7 @@ static ScenarioMap_t make_scenarios() auto const& [ - scnCommon, matVisual, physics, shapeSpawn, + commonScene, matVisual, physics, shapeSpawn, prefabs, parts, vehicleSpawn, vehicleSpawnVB, vehicleSpawnRgd, signalsFloat, machRocket, machRcsDriver, @@ -264,19 +348,81 @@ static ScenarioMap_t make_scenarios() newton, nwtGravSet, nwtGrav, shapeSpawnNwt, vehicleSpawnNwt, nwtRocketSet, rocketsNwt ] = unpack<24>(scene); - rendererOut.resize(6); - auto & [scnRender, cameraCtrl, shVisual, camThrow, vehicleCtrl, cameraVehicle] = unpack<6>(rendererOut); - scnRender = setup_scene_renderer (builder, rTopData, rTags, magnum, scnCommon, mainView.m_idResources); + auto & [scnRender, cameraCtrl, shPhong, shFlat, camThrow, vehicleCtrl, cameraVehicle, thrustIndicator] = resize_then_unpack<8>(rendererOut); + scnRender = setup_scene_renderer (builder, rTopData, rTags, magnum, commonScene, mainView.m_idResources); cameraCtrl = setup_camera_ctrl (builder, rTopData, rTags, magnum, scnRender); - shVisual = setup_shader_visualizer (builder, rTopData, rTags, magnum, scnCommon, scnRender, matVisual); + shPhong = setup_shader_phong (builder, rTopData, rTags, magnum, commonScene, scnRender, matVisual); + shFlat = setup_shader_flat (builder, rTopData, rTags, magnum, commonScene, scnRender, {}); camThrow = setup_thrower (builder, rTopData, rTags, magnum, scnRender, cameraCtrl, shapeSpawn); - vehicleCtrl = setup_vehicle_control (builder, rTopData, rTags, scnCommon, parts, signalsFloat, magnum); - cameraVehicle = setup_camera_vehicle (builder, rTopData, rTags, magnum, scnCommon, parts, physics, cameraCtrl, vehicleCtrl); + vehicleCtrl = setup_vehicle_control (builder, rTopData, rTags, commonScene, parts, signalsFloat, magnum); + cameraVehicle = setup_camera_vehicle (builder, rTopData, rTags, magnum, commonScene, parts, physics, cameraCtrl, vehicleCtrl); + thrustIndicator = setup_thrust_indicators (builder, rTopData, rTags, magnum, commonScene, parts, signalsFloat, scnRender, cameraCtrl, shFlat, mainView.m_idResources, mainView.m_defaultPkg); + + setup_magnum_draw(mainView, magnum, commonScene, scnRender); + }; + }); + + add_scenario("universe", "Universe test scenario with very unrealistic planets", + [] (MainView mainView, Sessions_t& sceneOut) -> RendererSetup_t + { + using namespace testapp::scenes; + + auto const idResources = mainView.m_idResources; + auto &rTopData = mainView.m_topData; + auto &rTags = mainView.m_rTags; + Builder_t builder{rTags, mainView.m_rTasks, mainView.m_rTaskData}; + + auto & + [ + commonScene, matVisual, physics, shapeSpawn, droppers, bounds, newton, nwtGravSet, nwtGrav, shapeSpawnNwt, uniCore, uniScnFrame, uniTestPlanets + ] = resize_then_unpack<13>(sceneOut); - setup_magnum_draw(mainView, magnum, scnCommon, scnRender); + // Compose together lots of Sessions + commonScene = setup_common_scene (builder, rTopData, rTags, idResources, mainView.m_defaultPkg); + matVisual = setup_material (builder, rTopData, rTags, commonScene); + physics = setup_physics (builder, rTopData, rTags, commonScene); + shapeSpawn = setup_shape_spawn (builder, rTopData, rTags, commonScene, physics, matVisual); + droppers = setup_droppers (builder, rTopData, rTags, commonScene, shapeSpawn); + bounds = setup_bounds (builder, rTopData, rTags, commonScene, physics, shapeSpawn); + + newton = setup_newton (builder, rTopData, rTags, commonScene, physics); + nwtGravSet = setup_newton_factors (builder, rTopData, rTags); + nwtGrav = setup_newton_force_accel (builder, rTopData, rTags, newton, nwtGravSet, Vector3{0.0f, 0.0f, -9.81f}); + shapeSpawnNwt = setup_shape_spawn_newton (builder, rTopData, rTags, commonScene, physics, shapeSpawn, newton, nwtGravSet); + + uniCore = setup_uni_core (builder, rTopData, rTags); + uniScnFrame = setup_uni_sceneframe (builder, rTopData, rTags); + uniTestPlanets = setup_uni_test_planets (builder, rTopData, rTags, uniCore, uniScnFrame); + + add_floor(rTopData, commonScene, matVisual, shapeSpawn, idResources, mainView.m_defaultPkg); + + return [] (MainView mainView, Session const& magnum, Sessions_t const& scene, Sessions_t& rendererOut) + { + auto &rTopData = mainView.m_topData; + auto &rTags = mainView.m_rTags; + Builder_t builder{mainView.m_rTags, mainView.m_rTasks, mainView.m_rTaskData}; + + auto const& [commonScene, matVisual, physics, shapeSpawn, droppers, bounds, newton, nwtGravSet, nwtGrav, shapeSpawnNwt, uniCore, uniScnFrame, uniTestPlanets] = unpack<13>(scene); + + rendererOut.resize(8); + auto & [scnRender, cameraCtrl, cameraFree, shFlat, shVisual, camThrow, cursor, uniTestPlanetsRdr] = unpack<8>(rendererOut); + scnRender = setup_scene_renderer (builder, rTopData, rTags, magnum, commonScene, mainView.m_idResources); + cameraCtrl = setup_camera_ctrl (builder, rTopData, rTags, magnum, scnRender); + cameraFree = setup_camera_free (builder, rTopData, rTags, magnum, commonScene, cameraCtrl); + shFlat = setup_shader_flat (builder, rTopData, rTags, magnum, commonScene, scnRender, {}); + shVisual = setup_shader_visualizer (builder, rTopData, rTags, magnum, commonScene, scnRender, matVisual); + camThrow = setup_thrower (builder, rTopData, rTags, magnum, scnRender, cameraCtrl, shapeSpawn); + cursor = setup_cursor (builder, rTopData, rTags, magnum, commonScene, scnRender, cameraCtrl, shFlat, mainView.m_idResources, mainView.m_defaultPkg); + uniTestPlanetsRdr = setup_uni_test_planets_renderer (builder, rTopData, rTags, magnum, scnRender, commonScene, cameraCtrl, shVisual, uniCore, uniScnFrame, uniTestPlanets); + + OSP_SESSION_UNPACK_TAGS(uniCore, TESTAPP_UNI_CORE); + + setup_magnum_draw(mainView, magnum, commonScene, scnRender, {tgUniTimeEvt}); }; }); +#endif + return scenarioMap; } diff --git a/src/test_application/activescenes/scenarios.h b/src/test_application/activescenes/scenarios.h index 611cb111..8a8eb8af 100644 --- a/src/test_application/activescenes/scenarios.h +++ b/src/test_application/activescenes/scenarios.h @@ -24,50 +24,45 @@ */ #pragma once -#include +#include "identifiers.h" -#include +#include "../testapp.h" -#include -#include -#include -#include -#include +// IWYU pragma: begin_exports +#include +// IWYU pragma: end_exports -#include -#include #include -namespace osp::active { struct RenderGL; } -namespace osp::input { class UserInputHandler; } - namespace testapp { -struct MainView +namespace scenes { - osp::ArrayView m_topData; - osp::Tags & m_rTags; - osp::Tasks & m_rTasks; - osp::ExecutionContext & m_rExec; - osp::TopTaskDataVec_t & m_rTaskData; - osp::TopDataId m_idResources; -}; - -using Builder_t = osp::TaskBuilder; + using enum EStgOptn; + using enum EStgCont; + using enum EStgIntr; + using enum EStgRevd; + using enum EStgEvnt; + using enum EStgFBO; +} -using RendererSetup_t = void(*)(MainView, osp::Session const&, osp::Sessions_t const&, osp::Sessions_t&); -using SceneSetup_t = RendererSetup_t(*)(MainView, osp::PkgId, osp::Sessions_t&); +struct MainLoopControl +{ + bool doUpdate; + bool doSync; + bool doResync; + bool doRender; +}; struct ScenarioOption { std::string_view m_desc; - SceneSetup_t m_setup; + SceneSetupFunc_t m_setup; }; using ScenarioMap_t = std::unordered_map; ScenarioMap_t const& scenarios(); - } // namespace testapp diff --git a/src/test_application/activescenes/scenarios_enginetest.cpp b/src/test_application/activescenes/scenarios_enginetest.cpp index 897e71b6..15442921 100644 --- a/src/test_application/activescenes/scenarios_enginetest.cpp +++ b/src/test_application/activescenes/scenarios_enginetest.cpp @@ -24,7 +24,7 @@ */ #include "CameraController.h" -#include "../ActiveApplication.h" +#include "../MagnumApplication.h" #include #include @@ -96,8 +96,9 @@ struct EngineTestScene ActiveEnt m_cube{lgrn::id_null()}; // Set of ActiveEnts that are assigned a Phong material - osp::active::EntSet_t m_matPhong; - osp::active::EntVector_t m_matPhongDirty; + osp::active::ActiveEntSet_t m_matPhong; + std::vector m_matPhongDirty; + }; EngineTestScene::~EngineTestScene() @@ -124,12 +125,14 @@ entt::any setup_scene(osp::Resources& rResources, osp::PkgId const pkg) rScene.m_pResources = &rResources; // Make a cube - rScene.m_cube = rScene.m_activeIds.create(); + ActiveEnt const cubeEnt = rScene.m_activeIds.create(); + DrawEnt const cubeDraw = rScene.m_drawing.m_drawIds.create(); // Resize some containers to fit all existing entities - rScene.m_matPhong.ints().resize(rScene.m_activeIds.vec().capacity()); - rScene.m_drawing.m_drawable.ints().resize(rScene.m_activeIds.vec().capacity()); - rScene.m_basic.m_scnGraph.resize(rScene.m_activeIds.capacity()); + rScene.m_matPhong.ints() .resize(rScene.m_activeIds.vec().capacity()); + rScene.m_basic.m_scnGraph .resize(rScene.m_activeIds.capacity()); + rScene.m_drawing.resize_active(rScene.m_activeIds.capacity()); + rScene.m_drawing.resize_draw(); // Take ownership of the cube mesh Resource. This will create a scene-space // MeshId that we can assign to ActiveEnts @@ -138,25 +141,28 @@ entt::any setup_scene(osp::Resources& rResources, osp::PkgId const pkg) MeshId const meshCube = SysRender::own_mesh_resource(rScene.m_drawing, rScene.m_drawingRes, rResources, resCube); // Add cube mesh to cube - rScene.m_drawing.m_mesh.emplace( - rScene.m_cube, rScene.m_drawing.m_meshRefCounts.ref_add(meshCube)); - rScene.m_drawing.m_meshDirty.push_back(rScene.m_cube); + + rScene.m_drawing.m_needDrawTf.set(std::size_t(cubeEnt)); + rScene.m_drawing.m_activeToDraw[cubeEnt] = cubeDraw; + rScene.m_drawing.m_mesh[cubeDraw] = rScene.m_drawing.m_meshRefCounts.ref_add(meshCube); + rScene.m_drawing.m_meshDirty.push_back(cubeDraw); // Add transform - rScene.m_basic.m_transform.emplace(rScene.m_cube); + rScene.m_basic.m_transform.emplace(cubeEnt); // Add phong material to cube - rScene.m_matPhong.set(std::size_t(rScene.m_cube)); - rScene.m_matPhongDirty.push_back(rScene.m_cube); + rScene.m_matPhong.set(std::size_t(cubeDraw)); + rScene.m_matPhongDirty.push_back(cubeDraw); // Add drawble, opaque, and visible component - rScene.m_drawing.m_drawable.set(std::size_t(rScene.m_cube)); - rScene.m_drawing.m_opaque.emplace(rScene.m_cube); - rScene.m_drawing.m_visible.emplace(rScene.m_cube); + rScene.m_drawing.m_visible.set(std::size_t(cubeDraw)); + rScene.m_drawing.m_drawBasic[cubeDraw].m_opaque = true; // Add cube to hierarchy, parented to root SubtreeBuilder builder = SysSceneGraph::add_descendants(rScene.m_basic.m_scnGraph, 1); - builder.add_child(rScene.m_cube); + builder.add_child(cubeEnt); + + rScene.m_cube = cubeEnt; return sceneAny; } @@ -169,7 +175,8 @@ entt::any setup_scene(osp::Resources& rResources, osp::PkgId const pkg) void update_test_scene(EngineTestScene& rScene, float const delta) { // Clear drawing-related dirty flags/vectors - osp::active::SysRender::clear_dirty_all(rScene.m_drawing); + rScene.m_drawing.m_meshDirty.clear(); + rScene.m_drawing.m_diffuseDirty.clear(); rScene.m_matPhongDirty.clear(); // Rotate the cube @@ -197,7 +204,7 @@ struct EngineTestRenderer // Support for assigning render-space GL meshes/textures and transforms // for ActiveEnts - osp::active::ACtxSceneRenderGL m_renderGl{}; + osp::active::ACtxSceneRenderGL m_sceneRenderGL{}; // Pre-built easy camera controls osp::active::Camera m_cam; @@ -225,31 +232,39 @@ void sync_test_scene( using namespace osp::active; using namespace osp::shader; + rRenderer.m_sceneRenderGL.m_drawTransform.resize(rScene.m_drawing.m_drawIds.capacity()); + rRenderer.m_sceneRenderGL.m_diffuseTexId.resize(rScene.m_drawing.m_drawIds.capacity()); + rRenderer.m_sceneRenderGL.m_meshId.resize(rScene.m_drawing.m_drawIds.capacity()); + // Assign or remove phong shaders from entities marked dirty sync_phong( std::cbegin(rScene.m_matPhongDirty), std::cend(rScene.m_matPhongDirty), rScene.m_matPhong, &rRenderer.m_groupFwdOpaque.m_entities, nullptr, - rScene.m_drawing.m_opaque, rRenderer.m_renderGl.m_diffuseTexId, + rScene.m_drawing.m_drawBasic, rRenderer.m_sceneRenderGL.m_diffuseTexId, rRenderer.m_phong); - SysRender::assure_draw_transforms( - rRenderer.m_renderGl.m_drawTransform, - std::cbegin(rScene.m_matPhongDirty), - std::cend(rScene.m_matPhongDirty)); - // Load required meshes and textures into OpenGL - SysRenderGL::sync_scene_resources(rScene.m_drawingRes, *rScene.m_pResources, rRenderGl); + SysRenderGL::compile_resource_meshes (rScene.m_drawingRes, *rScene.m_pResources, rRenderGl); + SysRenderGL::compile_resource_textures(rScene.m_drawingRes, *rScene.m_pResources, rRenderGl); // Assign GL meshes to entities with a mesh component - SysRenderGL::assign_meshes( - rScene.m_drawing.m_mesh, rScene.m_drawingRes.m_meshToRes, rScene.m_drawing.m_meshDirty, - rRenderer.m_renderGl.m_meshId, rRenderGl); + SysRenderGL::sync_drawent_mesh( + rScene.m_drawing.m_meshDirty.begin(), + rScene.m_drawing.m_meshDirty.end(), + rScene.m_drawing.m_mesh, + rScene.m_drawingRes.m_meshToRes, + rRenderer.m_sceneRenderGL.m_meshId, + rRenderGl); // Assign GL textures to entities with a texture component - SysRenderGL::assign_textures( - rScene.m_drawing.m_diffuseTex, rScene.m_drawingRes.m_texToRes, rScene.m_drawing.m_diffuseDirty, - rRenderer.m_renderGl.m_diffuseTexId, rRenderGl); + SysRenderGL::sync_drawent_texture( + rScene.m_drawing.m_meshDirty.begin(), + rScene.m_drawing.m_meshDirty.end(), + rScene.m_drawing.m_diffuseTex, + rScene.m_drawingRes.m_texToRes, + rRenderer.m_sceneRenderGL.m_diffuseTexId, + rRenderGl); // Calculate hierarchy transforms @@ -257,9 +272,12 @@ void sync_test_scene( SysRender::update_draw_transforms( rScene.m_basic.m_scnGraph, + rScene.m_drawing.m_activeToDraw, rScene.m_basic.m_transform, - rRenderer.m_renderGl.m_drawTransform, - rScene.m_drawing.m_drawable, drawTfDirty.begin(), drawTfDirty.end()); + rRenderer.m_sceneRenderGL.m_drawTransform, + rScene.m_drawing.m_needDrawTf, + drawTfDirty.begin(), + drawTfDirty.end()); } /** @@ -301,25 +319,61 @@ void render_test_scene( SysRenderGL::display_texture(rRenderGl, rFboColor); } -on_draw_t generate_draw_func(EngineTestScene& rScene, ActiveApplication &rApp, RenderGL& rRenderGl, UserInputHandler& rUserInput) +class EngineTestApp : public IOspApplication +{ +public: + EngineTestApp(EngineTestRenderer renderer, EngineTestScene& rScene, RenderGL& rRenderGl) + : m_renderer {std::move(renderer)} + , m_rScene {rScene} + , m_rRenderGl {rRenderGl} + { } + + ~EngineTestApp() override + { }; + + void run(MagnumApplication& rApp) override + { } + + void draw(MagnumApplication& rApp, float delta) override + { + update_test_scene(m_rScene, delta); + + // Rotate and move the camera based on user inputs + SysCameraController::update_view(m_renderer.m_camCtrl, delta); + SysCameraController::update_move(m_renderer.m_camCtrl, delta, true); + m_renderer.m_cam.m_transform = m_renderer.m_camCtrl.m_transform; + + sync_test_scene (m_rRenderGl, m_rScene, m_renderer); + render_test_scene(m_rRenderGl, m_rScene, m_renderer); + } + + void exit(MagnumApplication& rApp) override + { } + + EngineTestRenderer m_renderer; + + EngineTestScene &m_rScene; + RenderGL &m_rRenderGl; +}; + +MagnumApplication::AppPtr_t generate_draw_func(EngineTestScene& rScene, MagnumApplication &rApp, RenderGL& rRenderGl, UserInputHandler& rUserInput) { using namespace osp::active; using namespace osp::shader; - // Create renderer data. This uses a shared_ptr to allow being stored - // inside an std::function, which require copyable types - std::shared_ptr pRenderer - = std::make_shared(rUserInput); + auto pApp = std::make_unique(EngineTestRenderer{rUserInput}, rScene, rRenderGl); + + EngineTestRenderer &rRenderer = pApp->m_renderer; // Create Phong shaders auto const texturedFlags = Phong::Flag::DiffuseTexture | Phong::Flag::AlphaMask | Phong::Flag::AmbientTexture; - pRenderer->m_phong.m_shaderDiffuse = Phong{Phong::Configuration{}.setFlags(texturedFlags).setLightCount(2)}; - pRenderer->m_phong.m_shaderUntextured = Phong{Phong::Configuration{}.setLightCount(2)}; - pRenderer->m_phong.assign_pointers(pRenderer->m_renderGl, rRenderGl); + rRenderer.m_phong.m_shaderDiffuse = Phong{Phong::Configuration{}.setFlags(texturedFlags).setLightCount(2)}; + rRenderer.m_phong.m_shaderUntextured = Phong{Phong::Configuration{}.setLightCount(2)}; + rRenderer.m_phong.assign_pointers(rRenderer.m_sceneRenderGL, rRenderGl); - pRenderer->m_cam.set_aspect_ratio( + rRenderer.m_cam.set_aspect_ratio( osp::Vector2(Magnum::GL::defaultFramebuffer.viewport().size())); // Set all drawing stuff dirty then sync with renderer. @@ -327,24 +381,12 @@ on_draw_t generate_draw_func(EngineTestScene& rScene, ActiveApplication &rApp, R SysRender::set_dirty_all(rScene.m_drawing); for (std::size_t const entInt : rScene.m_matPhong.ones()) { - rScene.m_matPhongDirty.push_back(ActiveEnt(entInt)); + rScene.m_matPhongDirty.push_back(DrawEnt(entInt)); } - sync_test_scene(rRenderGl, rScene, *pRenderer); - - return [&rScene, pRenderer = std::move(pRenderer), &rRenderGl] ( - ActiveApplication& rApp, float delta) - { - update_test_scene(rScene, delta); - - // Rotate and move the camera based on user inputs - SysCameraController::update_view(pRenderer->m_camCtrl, delta); - SysCameraController::update_move(pRenderer->m_camCtrl, delta, true); - pRenderer->m_cam.m_transform = pRenderer->m_camCtrl.m_transform; + sync_test_scene(rRenderGl, rScene, rRenderer); - sync_test_scene(rRenderGl, rScene, *pRenderer); - render_test_scene(rRenderGl, rScene, *pRenderer); - }; + return pApp; } } // namespace testapp::enginetest diff --git a/src/test_application/activescenes/scenarios_enginetest.h b/src/test_application/activescenes/scenarios_enginetest.h index cd6d8378..b3e48083 100644 --- a/src/test_application/activescenes/scenarios_enginetest.h +++ b/src/test_application/activescenes/scenarios_enginetest.h @@ -24,7 +24,9 @@ */ #pragma once -#include "../ActiveApplication.h" +#include "../MagnumApplication.h" + +#include namespace testapp::enginetest { @@ -42,17 +44,17 @@ struct EngineTestScene; entt::any setup_scene(osp::Resources& rResources, osp::PkgId pkg); /** - * @brief Generate ActiveApplication draw function + * @brief Generate MagnumApplication draw function * * This draw function stores renderer data, and is responsible for updating * and drawing the engine test scene. * * @param rScene [ref] Engine test scene. Must be in stable memory. - * @param rApp [ref] Existing ActiveApplication to use GL resources of + * @param rApp [ref] Existing MagnumApplication to use GL resources of * - * @return ActiveApplication draw function + * @return MagnumApplication draw function */ -on_draw_t generate_draw_func(EngineTestScene& rScene, ActiveApplication& rApp, osp::active::RenderGL& rRenderGl, osp::input::UserInputHandler& rUserInput); +MagnumApplication::AppPtr_t generate_draw_func(EngineTestScene& rScene, MagnumApplication& rApp, osp::active::RenderGL& rRenderGl, osp::input::UserInputHandler& rUserInput); } // namespace testapp::enginetest diff --git a/src/test_application/activescenes/scene_common.cpp b/src/test_application/activescenes/scene_common.cpp index c5565d26..9ea64c20 100644 --- a/src/test_application/activescenes/scene_common.cpp +++ b/src/test_application/activescenes/scene_common.cpp @@ -24,7 +24,6 @@ */ #include "scene_common.h" #include "scenarios.h" -#include "identifiers.h" #include #include @@ -42,150 +41,256 @@ using namespace osp::active; namespace testapp::scenes { +Session setup_scene( + TopTaskBuilder& rBuilder, + ArrayView const topData, + Session const& application) +{ + OSP_DECLARE_GET_DATA_IDS(application, TESTAPP_DATA_APPLICATION); + auto const tgApp = application.get_pipelines< PlApplication >(); + + osp::Session out; + OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_SCENE); + + top_emplace< float >(topData, idDeltaTimeIn, 1.0f / 60.0f); + + auto const plScn = out.create_pipelines(rBuilder); + + rBuilder.pipeline(plScn.update).parent(tgApp.mainLoop).wait_for_signal(ModifyOrSignal); + + rBuilder.task() + .name ("Schedule Scene update") + .schedules ({plScn.update(Schedule)}) + .push_to (out.m_tasks) + .args ({ idMainLoopCtrl}) + .func([] (MainLoopControl const& rMainLoopCtrl) noexcept -> osp::TaskActions + { + return rMainLoopCtrl.doUpdate ? osp::TaskActions{} : osp::TaskAction::Cancel; + }); + + return out; +} + Session setup_common_scene( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - TopDataId const idResources, + Session const& scene, + Session const& application, PkgId const pkg) { + OSP_DECLARE_GET_DATA_IDS(application, TESTAPP_DATA_APPLICATION); + + auto const tgScn = scene.get_pipelines(); auto &rResources = top_get< Resources > (topData, idResources); - Session scnCommon; - OSP_SESSION_ACQUIRE_DATA(scnCommon, topData, TESTAPP_COMMON_SCENE); - OSP_SESSION_ACQUIRE_TAGS(scnCommon, rTags, TESTAPP_COMMON_SCENE); - scnCommon.m_tgCleanupEvt = tgCleanupEvt; + Session out; + OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_COMMON_SCENE); + auto const tgCS = out.create_pipelines(rBuilder); - top_emplace< float > (topData, idDeltaTimeIn, 1.0f / 60.0f); - top_emplace< EntVector_t > (topData, idDelEnts); - top_emplace< EntVector_t > (topData, idDelTotal); + out.m_cleanup = tgScn.cleanup; + /* unused */ top_emplace< ActiveEntVec_t > (topData, idActiveEntDel); + /* unused */ top_emplace< DrawEntVec_t > (topData, idDrawEntDel); auto &rBasic = top_emplace< ACtxBasic > (topData, idBasic); - auto &rActiveIds = top_emplace< ActiveReg_t > (topData, idActiveIds); auto &rDrawing = top_emplace< ACtxDrawing > (topData, idDrawing); auto &rDrawingRes = top_emplace< ACtxDrawingRes > (topData, idDrawingRes); auto &rNMesh = top_emplace< NamedMeshes > (topData, idNMesh); - rBuilder.tag(tgEntNew) .depend_on({tgEntDel}); - rBuilder.tag(tgEntReq) .depend_on({tgEntDel, tgEntNew}); - rBuilder.tag(tgDelEntReq) .depend_on({tgDelEntMod}); - rBuilder.tag(tgDelEntClr) .depend_on({tgDelEntMod, tgDelEntReq}); - rBuilder.tag(tgDelTotalReq) .depend_on({tgDelTotalMod}); - rBuilder.tag(tgDelTotalClr) .depend_on({tgDelTotalMod, tgDelTotalReq}); - rBuilder.tag(tgTransformDel) .depend_on({tgTransformMod}); - rBuilder.tag(tgTransformNew) .depend_on({tgTransformMod, tgTransformDel}); - rBuilder.tag(tgTransformReq) .depend_on({tgTransformMod, tgTransformDel, tgTransformNew}); - rBuilder.tag(tgHierNew) .depend_on({tgHierDel}); - rBuilder.tag(tgHierModEnd) .depend_on({tgHierDel, tgHierNew, tgHierMod}); - rBuilder.tag(tgHierReq) .depend_on({tgHierMod, tgHierModEnd}); - rBuilder.tag(tgDrawMod) .depend_on({tgDrawDel}); - rBuilder.tag(tgDrawReq) .depend_on({tgDrawDel, tgDrawMod}); - rBuilder.tag(tgMeshMod) .depend_on({tgMeshDel}); - rBuilder.tag(tgMeshReq) .depend_on({tgMeshDel, tgMeshMod}); - rBuilder.tag(tgMeshClr) .depend_on({tgMeshDel, tgMeshMod, tgMeshReq}); - rBuilder.tag(tgTexMod) .depend_on({tgTexDel}); - rBuilder.tag(tgTexReq) .depend_on({tgTexDel, tgTexMod}); - rBuilder.tag(tgTexClr) .depend_on({tgTexDel, tgTexMod, tgTexReq}); - - scnCommon.task() = rBuilder.task().assign({tgResyncEvt}).data( - "Set entity meshes and textures dirty", - TopDataIds_t{ idDrawing}, - wrap_args([] (ACtxDrawing& rDrawing) noexcept + rBuilder.pipeline(tgCS.activeEnt) .parent(tgScn.update); + rBuilder.pipeline(tgCS.activeEntResized) .parent(tgScn.update); + rBuilder.pipeline(tgCS.activeEntDelete) .parent(tgScn.update); + rBuilder.pipeline(tgCS.transform) .parent(tgScn.update); + rBuilder.pipeline(tgCS.hierarchy) .parent(tgScn.update); + rBuilder.pipeline(tgCS.drawEnt) .parent(tgScn.update); + rBuilder.pipeline(tgCS.drawEntResized) .parent(tgScn.update); + rBuilder.pipeline(tgCS.drawEntDelete) .parent(tgScn.update); + rBuilder.pipeline(tgCS.mesh) .parent(tgScn.update); + rBuilder.pipeline(tgCS.texture) .parent(tgScn.update); + rBuilder.pipeline(tgCS.entMesh) .parent(tgScn.update); + rBuilder.pipeline(tgCS.entTexture) .parent(tgScn.update); + rBuilder.pipeline(tgCS.entTextureDirty) .parent(tgScn.update); + rBuilder.pipeline(tgCS.entMeshDirty) .parent(tgScn.update); + rBuilder.pipeline(tgCS.meshResDirty) .parent(tgScn.update); + rBuilder.pipeline(tgCS.textureResDirty) .parent(tgScn.update); + rBuilder.pipeline(tgCS.material) .parent(tgScn.update); + rBuilder.pipeline(tgCS.materialDirty) .parent(tgScn.update); + + rBuilder.task() + .name ("Cancel entity delete tasks stuff if no entities were deleted") + .run_on ({tgCS.activeEntDelete(Schedule_)}) + .push_to (out.m_tasks) + .args ({ idBasic, idActiveEntDel }) + .func([] (ACtxBasic& rBasic, ActiveEntVec_t const& rActiveEntDel) noexcept { - SysRender::set_dirty_all(rDrawing); - })); - - scnCommon.task() = rBuilder.task().assign({tgSceneEvt, tgDelEntReq, tgDelTotalMod}).data( - "Create DeleteTotal vector, which includes descendents of deleted hierarchy entities", - TopDataIds_t{ idBasic, idDelEnts, idDelTotal}, - wrap_args([] (ACtxBasic& rBasic, EntVector_t const& rDelEnts, EntVector_t& rDelTotal) noexcept + return rActiveEntDel.empty() ? TaskAction::Cancel : TaskActions{}; + }); + + rBuilder.task() + .name ("Delete ActiveEnt IDs") + .run_on ({tgCS.activeEntDelete(EStgIntr::UseOrRun)}) + .sync_with ({tgCS.activeEnt(Delete)}) + .push_to (out.m_tasks) + .args ({ idBasic, idActiveEntDel }) + .func([] (ACtxBasic& rBasic, ActiveEntVec_t const& rActiveEntDel) noexcept { - auto const &delFirst = std::cbegin(rDelEnts); - auto const &delLast = std::cend(rDelEnts); - - rDelTotal.assign(delFirst, delLast); - - for (ActiveEnt root : rDelEnts) + for (ActiveEnt const ent : rActiveEntDel) { - for (ActiveEnt descendant : SysSceneGraph::descendants(rBasic.m_scnGraph, root)) + if (rBasic.m_activeIds.exists(ent)) { - rDelTotal.push_back(descendant); + rBasic.m_activeIds.remove(ent); } } - })); - - scnCommon.task() = rBuilder.task().assign({tgSceneEvt, tgDelEntReq, tgHierMod}).data( - "Cut deleted entities out of hierarchy", - TopDataIds_t{ idBasic, idDelEnts}, - wrap_args([] (ACtxBasic& rBasic, EntVector_t const& rDelEnts) noexcept + }); + + rBuilder.task() + .name ("Delete basic components") + .run_on ({tgCS.activeEntDelete(UseOrRun)}) + .sync_with ({tgCS.transform(Delete)}) + .push_to (out.m_tasks) + .args ({ idBasic, idActiveEntDel }) + .func([] (ACtxBasic& rBasic, ActiveEntVec_t const& rActiveEntDel) noexcept { - auto const &delFirst = std::cbegin(rDelEnts); - auto const &delLast = std::cend(rDelEnts); - - SysSceneGraph::cut(rBasic.m_scnGraph, delFirst, delLast); - })); - - scnCommon.task() = rBuilder.task().assign({tgSceneEvt, tgDelTotalReq, tgEntDel}).data( - "Delete Entity IDs", - TopDataIds_t{ idActiveIds, idDelTotal}, - wrap_args([] (ActiveReg_t& rActiveIds, EntVector_t const& rDelTotal) noexcept + update_delete_basic(rBasic, rActiveEntDel.cbegin(), rActiveEntDel.cend()); + }); + + rBuilder.task() + .name ("Delete DrawEntity of deleted ActiveEnts") + .run_on ({tgCS.activeEntDelete(UseOrRun)}) + .sync_with ({tgCS.drawEntDelete(Modify_)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idActiveEntDel, idDrawEntDel }) + .func([] (ACtxDrawing& rDrawing, ActiveEntVec_t const& rActiveEntDel, DrawEntVec_t& rDrawEntDel) noexcept { - for (ActiveEnt const ent : rDelTotal) + for (ActiveEnt const ent : rActiveEntDel) { - if (rActiveIds.exists(ent)) + DrawEnt const drawEnt = std::exchange(rDrawing.m_activeToDraw[ent], lgrn::id_null()); + if (drawEnt != lgrn::id_null()) { - rActiveIds.remove(ent); + rDrawEntDel.push_back(drawEnt); } } - })); - - scnCommon.task() = rBuilder.task().assign({tgSceneEvt, tgDelTotalReq, tgTransformDel, tgHierDel}).data( - "Delete basic components", - TopDataIds_t{ idBasic, idDelTotal}, - wrap_args([] (ACtxBasic& rBasic, EntVector_t const& rDelTotal) noexcept + }); + + rBuilder.task() + .name ("Delete drawing components") + .run_on ({tgCS.drawEntDelete(UseOrRun)}) + .sync_with ({tgCS.entTexture(Delete), tgCS.entMesh(Delete)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idDrawEntDel }) + .func([] (ACtxDrawing& rDrawing, DrawEntVec_t const& rDrawEntDel) noexcept { - update_delete_basic(rBasic, std::cbegin(rDelTotal), std::cend(rDelTotal)); - })); - - scnCommon.task() = rBuilder.task().assign({tgSceneEvt, tgDelTotalReq, tgDrawDel}).data( - "Delete drawing components", - TopDataIds_t{ idDrawing, idDelTotal}, - wrap_args([] (ACtxDrawing& rDrawing, EntVector_t const& rDelTotal) noexcept + SysRender::update_delete_drawing(rDrawing, rDrawEntDel.cbegin(), rDrawEntDel.cend()); + }); + + rBuilder.task() + .name ("Delete DrawEntity IDs") + .run_on ({tgCS.drawEntDelete(UseOrRun)}) + .sync_with ({tgCS.drawEnt(Delete)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idDrawEntDel }) + .func([] (ACtxDrawing& rDrawing, DrawEntVec_t const& rDrawEntDel) noexcept { - SysRender::update_delete_drawing(rDrawing, std::cbegin(rDelTotal), std::cend(rDelTotal)); - })); - - scnCommon.task() = rBuilder.task().assign({tgSceneEvt, tgDelEntClr}).data( - "Clear delete vectors once we're done with it", - TopDataIds_t{ idDelEnts}, - wrap_args([] (EntVector_t& rDelEnts) noexcept + for (DrawEnt const drawEnt : rDrawEntDel) + { + if (rDrawing.m_drawIds.exists(drawEnt)) + { + rDrawing.m_drawIds.remove(drawEnt); + } + } + }); + + rBuilder.task() + .name ("Delete DrawEnt from materials") + .run_on ({tgCS.drawEntDelete(UseOrRun)}) + .sync_with ({tgCS.material(Delete)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idDrawEntDel }) + .func([] (ACtxDrawing& rDrawing, DrawEntVec_t const& rDrawEntDel) noexcept { - rDelEnts.clear(); - })); - - scnCommon.task() = rBuilder.task().assign({tgCleanupEvt}).data( - "Clean up scene and resource owners", - TopDataIds_t{ idDrawing, idDrawingRes, idResources}, - wrap_args([] (ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, Resources& rResources) noexcept + for (DrawEnt const ent : rDrawEntDel) + { + for (Material &rMat : rDrawing.m_materials) + { + if (std::size_t(ent) < rMat.m_ents.size()) + { + rMat.m_ents.reset(std::size_t(ent)); + } + } + } + }); + + rBuilder.task() + .name ("Clear ActiveEnt delete vector once we're done with it") + .run_on ({tgCS.activeEntDelete(Clear)}) + .push_to (out.m_tasks) + .args ({ idActiveEntDel }) + .func([] (ActiveEntVec_t& idActiveEntDel) noexcept { - SysRender::clear_owners(rDrawing); - SysRender::clear_resource_owners(rDrawingRes, rResources); - })); + idActiveEntDel.clear(); + }); + + rBuilder.task() + .name ("Clear DrawEnt delete vector once we're done with it") + .run_on ({tgCS.drawEntDelete(Clear)}) + .push_to (out.m_tasks) + .args ({ idDrawEntDel }) + .func([] (DrawEntVec_t& rDrawEntDel) noexcept + { + rDrawEntDel.clear(); + }); + + rBuilder.task() + .name ("Clear material dirty vectors once we're done with it") + .run_on ({tgCS.materialDirty(Clear)}) + .push_to (out.m_tasks) + .args ({ idDrawing }) + .func([] (ACtxDrawing& rDrawing) noexcept + { + for (std::size_t const materialInt : rDrawing.m_materialIds.bitview().zeros()) + { + rDrawing.m_materials[MaterialId(materialInt)].m_dirty.clear(); + } + }); + + rBuilder.task() + .name ("Clear dirty DrawEnt's textures once we're done with it") + .run_on ({tgCS.entMeshDirty(Clear)}) + .push_to (out.m_tasks) + .args ({ idDrawing}) + .func([] (ACtxDrawing& rDrawing) noexcept + { + rDrawing.m_meshDirty.clear(); + }); + + rBuilder.task() + .name ("Clear dirty DrawEnt's textures once we're done with it") + .run_on ({tgCS.entTextureDirty(Clear)}) + .push_to (out.m_tasks) + .args ({ idDrawing}) + .func([] (ACtxDrawing& rDrawing) noexcept + { + rDrawing.m_diffuseDirty.clear(); + }); + // Clean up tasks - // Convenient function to get a reference-counted mesh owner - auto const quick_add_mesh = [&rResources, &rDrawing, &rDrawingRes, pkg] (std::string_view const name) -> MeshIdOwner_t + rBuilder.task() + .name ("Clean up scene and resource owners") + .run_on ({tgScn.cleanup(Run_)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idDrawingRes, idResources}) + .func([] (ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, Resources& rResources) noexcept { - osp::ResId const res = rResources.find(osp::restypes::gc_mesh, pkg, name); - assert(res != lgrn::id_null()); - MeshId const meshId = SysRender::own_mesh_resource(rDrawing, rDrawingRes, rResources, res); - return rDrawing.m_meshRefCounts.ref_add(meshId); - }; - - scnCommon.task() = rBuilder.task().assign({tgCleanupEvt}).data( - "Clean up NamedMeshes mesh and texture owners", - TopDataIds_t{ idDrawing, idNMesh}, - wrap_args([] (ACtxDrawing& rDrawing, NamedMeshes& rNMesh) noexcept + SysRender::clear_owners(rDrawing); + SysRender::clear_resource_owners(rDrawingRes, rResources); + }); + + rBuilder.task() + .name ("Clean up NamedMeshes mesh and texture owners") + .run_on ({tgScn.cleanup(Run_)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idNMesh }) + .func([] (ACtxDrawing& rDrawing, NamedMeshes& rNMesh) noexcept { for ([[maybe_unused]] auto && [_, rOwner] : std::exchange(rNMesh.m_shapeToMesh, {})) { @@ -196,7 +301,10 @@ Session setup_common_scene( { rDrawing.m_meshRefCounts.ref_release(std::move(rOwner)); } - })); + }); + + // Convenient functor to get a reference-counted mesh owner + auto const quick_add_mesh = SysRender::gen_drawable_mesh_adder(rDrawing, rDrawingRes, rResources, pkg); // Acquire mesh resources from Package using osp::phys::EShape; @@ -205,59 +313,7 @@ Session setup_common_scene( rNMesh.m_shapeToMesh.emplace(EShape::Sphere, quick_add_mesh("sphere")); rNMesh.m_namedMeshs.emplace("floor", quick_add_mesh("grid64solid")); - - return scnCommon; + return out; } -Session setup_material( - Builder_t& rBuilder, - ArrayView const topData, - Tags& rTags, - Session const& scnCommon) -{ - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); - - Session material; - OSP_SESSION_ACQUIRE_DATA(material, topData, TESTAPP_MATERIAL); - OSP_SESSION_ACQUIRE_TAGS(material, rTags, TESTAPP_MATERIAL); - - top_emplace< EntSet_t > (topData, idMatEnts); - top_emplace< EntVector_t > (topData, idMatDirty); - - rBuilder.tag(tgMatMod) .depend_on({tgMatDel}); - rBuilder.tag(tgMatReq) .depend_on({tgMatDel, tgMatMod}); - rBuilder.tag(tgMatClr) .depend_on({tgMatDel, tgMatMod, tgMatReq}); - - material.task() = rBuilder.task().assign({tgResyncEvt}).data( - "Set all X material entities as dirty", - TopDataIds_t{ idMatEnts, idMatDirty}, - wrap_args([] (EntSet_t const& rMatEnts, EntVector_t& rMatDirty) noexcept - { - for (std::size_t const entInt : rMatEnts.ones()) - { - rMatDirty.push_back(ActiveEnt(entInt)); - } - })); - - material.task() = rBuilder.task().assign({tgSceneEvt, tgSyncEvt, tgMatClr}).data( - "Clear dirty vectors for material", - TopDataIds_t{ idMatDirty}, - wrap_args([] (EntVector_t& rMatDirty) noexcept - { - rMatDirty.clear(); - })); - - material.task() = rBuilder.task().assign({tgSceneEvt, tgDelTotalReq, tgMatDel}).data( - "Delete material components", - TopDataIds_t{idMatEnts, idDelTotal}, delete_ent_set); - - - return material; -} - -} - - - - +} // namespace testapp::scenes diff --git a/src/test_application/activescenes/scene_common.h b/src/test_application/activescenes/scene_common.h index e75e85c5..7dd2331c 100644 --- a/src/test_application/activescenes/scene_common.h +++ b/src/test_application/activescenes/scene_common.h @@ -35,14 +35,6 @@ namespace testapp::scenes { -inline auto const delete_ent_set = osp::wrap_args([] (osp::active::EntSet_t& rSet, osp::active::EntVector_t const& rDelTotal) noexcept -{ - for (osp::active::ActiveEnt const ent : rDelTotal) - { - rSet.reset(std::size_t(ent)); - } -}); - struct NamedMeshes { // Required for std::is_copy_assignable to work properly inside of entt::any @@ -56,25 +48,20 @@ struct NamedMeshes osp::active::MeshIdOwner_t> m_namedMeshs; }; +osp::Session setup_scene( + osp::TopTaskBuilder& rBuilder, + osp::ArrayView topData, + osp::Session const& application); + /** * @brief Support for Time, ActiveEnts, Hierarchy, Transforms, Drawing, and more... */ osp::Session setup_common_scene( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::TopDataId idResources, + osp::Session const& scene, + osp::Session const& application, osp::PkgId pkg); -/** - * @brief Support a single material, aka: a Set of ActiveEnts and dirty flags - * - * Multiple material sessions can be setup for each material - */ -osp::Session setup_material( - Builder_t& rBuilder, - osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon); } diff --git a/src/test_application/activescenes/scene_misc.cpp b/src/test_application/activescenes/scene_misc.cpp index 47d0f820..90b0d117 100644 --- a/src/test_application/activescenes/scene_misc.cpp +++ b/src/test_application/activescenes/scene_misc.cpp @@ -51,35 +51,45 @@ using namespace Magnum::Math::Literals; namespace testapp::scenes { +void create_materials( + ArrayView const topData, + Session const& commonScene, + int const count) +{ + OSP_DECLARE_GET_DATA_IDS(commonScene, TESTAPP_DATA_COMMON_SCENE); + auto &rDrawing = top_get< ACtxDrawing >(topData, idDrawing); + + for (int i = 0; i < count; ++i) + { + [[maybe_unused]] MaterialId const mat = rDrawing.m_materialIds.create(); + LGRN_ASSERT(int(mat) == i); + } + + rDrawing.m_materials.resize(count); +} + void add_floor( ArrayView const topData, - Session const& scnCommon, - Session const& material, + Session const& application, + Session const& commonScene, Session const& shapeSpawn, - TopDataId const idResources, + MaterialId const materialId, PkgId const pkg) { - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_DATA(material, TESTAPP_MATERIAL); - OSP_SESSION_UNPACK_DATA(shapeSpawn, TESTAPP_SHAPE_SPAWN); - - auto &rResources = top_get< Resources > (topData, idResources); - auto &rActiveIds = top_get< ActiveReg_t > (topData, idActiveIds); - auto &rBasic = top_get< ACtxBasic > (topData, idBasic); - auto &rDrawing = top_get< ACtxDrawing > (topData, idDrawing); - auto &rDrawingRes = top_get< ACtxDrawingRes > (topData, idDrawingRes); - auto &rMatEnts = top_get< EntSet_t > (topData, idMatEnts); - auto &rMatDirty = top_get< EntVector_t > (topData, idMatDirty); - auto &rSpawner = top_get< SpawnerVec_t > (topData, idSpawner); - - // Convenient function to get a reference-counted mesh owner - auto const quick_add_mesh = [&rResources, &rDrawing, &rDrawingRes, pkg] (std::string_view const name) -> MeshIdOwner_t - { - osp::ResId const res = rResources.find(osp::restypes::gc_mesh, pkg, name); - assert(res != lgrn::id_null()); - MeshId const meshId = SysRender::own_mesh_resource(rDrawing, rDrawingRes, rResources, res); - return rDrawing.m_meshRefCounts.ref_add(meshId); - }; + OSP_DECLARE_GET_DATA_IDS(application, TESTAPP_DATA_APPLICATION); + OSP_DECLARE_GET_DATA_IDS(commonScene, TESTAPP_DATA_COMMON_SCENE); + OSP_DECLARE_GET_DATA_IDS(shapeSpawn, TESTAPP_DATA_SHAPE_SPAWN); + + auto &rResources = top_get< Resources > (topData, idResources); + auto &rBasic = top_get< ACtxBasic > (topData, idBasic); + auto &rDrawing = top_get< ACtxDrawing > (topData, idDrawing); + auto &rDrawingRes = top_get< ACtxDrawingRes > (topData, idDrawingRes); + auto &rSpawner = top_get< ACtxShapeSpawner > (topData, idSpawner); + + Material &rMaterial = rDrawing.m_materials.at(materialId); + + // Convenient functor to get a reference-counted mesh owner + auto const quick_add_mesh = SysRender::gen_drawable_mesh_adder(rDrawing, rDrawingRes, rResources, pkg); // start making floor @@ -87,33 +97,33 @@ void add_floor( static constexpr Vector3 const sc_floorPos{0.0f, 0.0f, -1.005f}; // Create floor root and mesh entity - ActiveEnt const floorRootEnt = rActiveIds.create(); - ActiveEnt const floorMeshEnt = rActiveIds.create(); + ActiveEnt const floorRootEnt = rBasic.m_activeIds.create(); + ActiveEnt const floorMeshEnt = rBasic.m_activeIds.create(); + DrawEnt const floorMeshDrawEnt = rDrawing.m_drawIds.create(); // Resize some containers to fit all existing entities - rDrawing.m_drawable.ints().resize(rActiveIds.vec().capacity()); - rBasic.m_scnGraph.resize(rActiveIds.capacity()); + rBasic.m_scnGraph.resize(rBasic.m_activeIds.capacity()); + rDrawing.resize_active(rBasic.m_activeIds.capacity()); + rDrawing.resize_draw(); + bitvector_resize(rMaterial.m_ents, rDrawing.m_drawIds.capacity()); - // Add transform and draw transform to root rBasic.m_transform.emplace(floorRootEnt); // Add mesh to floor mesh entity - rDrawing.m_mesh.emplace(floorMeshEnt, quick_add_mesh("grid64solid")); - rDrawing.m_meshDirty.push_back(floorMeshEnt); + rDrawing.m_activeToDraw[floorMeshEnt] = floorMeshDrawEnt; + rDrawing.m_mesh[floorMeshDrawEnt] = quick_add_mesh("grid64solid"); + rDrawing.m_meshDirty.push_back(floorMeshDrawEnt); // Add mesh visualizer material to floor mesh entity - rMatEnts.ints().resize(rActiveIds.vec().capacity()); - rMatEnts.set(std::size_t(floorMeshEnt)); - rMatDirty.push_back(floorMeshEnt); + rMaterial.m_ents.set(std::size_t(floorMeshDrawEnt)); + rMaterial.m_dirty.push_back(floorMeshDrawEnt); // Add transform, draw transform, opaque, and visible entity - rBasic.m_transform.emplace( - floorMeshEnt, ACompTransform{Matrix4::scaling(sc_floorSize)}); - rDrawing.m_opaque.emplace(floorMeshEnt); - rDrawing.m_visible.emplace(floorMeshEnt); - - rDrawing.m_drawable.set(std::size_t(floorRootEnt)); - rDrawing.m_drawable.set(std::size_t(floorMeshEnt)); + rBasic.m_transform.emplace(floorMeshEnt, ACompTransform{Matrix4::scaling(sc_floorSize)}); + rDrawing.m_drawBasic[floorMeshDrawEnt].m_opaque = true; + rDrawing.m_visible.set(std::size_t(floorMeshDrawEnt)); + rDrawing.m_needDrawTf.set(std::size_t(floorRootEnt)); + rDrawing.m_needDrawTf.set(std::size_t(floorMeshEnt)); SubtreeBuilder builder = SysSceneGraph::add_descendants(rBasic.m_scnGraph, 2); @@ -124,7 +134,7 @@ void add_floor( bldFloorRoot.add_child(floorMeshEnt); // Add collider to floor root entity - rSpawner.emplace_back(SpawnShape{ + rSpawner.m_spawnRequest.emplace_back(SpawnShape{ .m_position = sc_floorPos, .m_velocity = sc_floorSize, .m_size = sc_floorSize, @@ -134,90 +144,99 @@ void add_floor( } Session setup_camera_ctrl( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& app, + Session const& windowApp, Session const& scnRender) { - OSP_SESSION_UNPACK_DATA(app, TESTAPP_APP); - OSP_SESSION_UNPACK_TAGS(app, TESTAPP_APP); - OSP_SESSION_UNPACK_DATA(scnRender, TESTAPP_COMMON_RENDERER); - OSP_SESSION_UNPACK_TAGS(scnRender, TESTAPP_COMMON_RENDERER); - auto &rUserInput = top_get< osp::input::UserInputHandler >(topData, idUserInput); + OSP_DECLARE_GET_DATA_IDS(windowApp, TESTAPP_DATA_WINDOW_APP); + OSP_DECLARE_GET_DATA_IDS(scnRender, TESTAPP_DATA_COMMON_RENDERER); + + auto const tgSR = scnRender.get_pipelines(); + //auto const tgWin = windowApp.get_pipelines(); - Session cameraCtrl; - OSP_SESSION_ACQUIRE_DATA(cameraCtrl, topData, TESTAPP_CAMERA_CTRL); - OSP_SESSION_ACQUIRE_TAGS(cameraCtrl, rTags, TESTAPP_CAMERA_CTRL); + auto &rUserInput = top_get< osp::input::UserInputHandler >(topData, idUserInput); - rBuilder.tag(tgCamCtrlReq).depend_on({tgCamCtrlMod}); + Session out; + OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_CAMERA_CTRL); + auto const tgCmCt = out.create_pipelines(rBuilder); top_emplace< ACtxCameraController > (topData, idCamCtrl, rUserInput); - cameraCtrl.task() = rBuilder.task().assign({tgRenderEvt, tgCamCtrlReq, tgCameraMod}).data( - "Position Rendering Camera according to Camera Controller", - TopDataIds_t{ idCamCtrl, idCamera}, - wrap_args([] (ACtxCameraController const& rCamCtrl, Camera &rCamera) noexcept + rBuilder.pipeline(tgCmCt.camCtrl).parent(tgSR.render); + + rBuilder.task() + .name ("Position Rendering Camera according to Camera Controller") + .run_on ({tgSR.render(Run)}) + .sync_with ({tgCmCt.camCtrl(Ready), tgSR.camera(Modify)}) + .push_to (out.m_tasks) + .args ({ idCamCtrl, idCamera }) + .func([] (ACtxCameraController const& rCamCtrl, Camera &rCamera) noexcept { rCamera.m_transform = rCamCtrl.m_transform; - })); + }); - return cameraCtrl; + return out; } Session setup_camera_free( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& app, - Session const& scnCommon, - Session const& camera) + Session const& windowApp, + Session const& scene, + Session const& cameraCtrl) { - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_TAGS(app, TESTAPP_APP); - OSP_SESSION_UNPACK_DATA(camera, TESTAPP_CAMERA_CTRL); - OSP_SESSION_UNPACK_TAGS(camera, TESTAPP_CAMERA_CTRL); + OSP_DECLARE_GET_DATA_IDS(scene, TESTAPP_DATA_SCENE); + OSP_DECLARE_GET_DATA_IDS(cameraCtrl, TESTAPP_DATA_CAMERA_CTRL); + + auto const tgWin = windowApp .get_pipelines(); + auto const tgCmCt = cameraCtrl .get_pipelines(); - Session cameraFree; + Session out; - cameraFree.task() = rBuilder.task().assign({tgInputEvt, tgCamCtrlMod}).data( - "Move Camera", - TopDataIds_t{ idCamCtrl, idDeltaTimeIn}, - wrap_args([] (ACtxCameraController& rCamCtrl, float const deltaTimeIn) noexcept + rBuilder.task() + .name ("Move Camera controller") + .run_on ({tgWin.inputs(Run)}) + .sync_with ({tgCmCt.camCtrl(Modify)}) + .push_to (out.m_tasks) + .args ({ idCamCtrl, idDeltaTimeIn }) + .func([] (ACtxCameraController& rCamCtrl, float const deltaTimeIn) noexcept { SysCameraController::update_view(rCamCtrl, deltaTimeIn); SysCameraController::update_move(rCamCtrl, deltaTimeIn, true); - })); + }); - return cameraFree; + return out; } + Session setup_thrower( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& magnum, - Session const& renderer, - Session const& simpleCamera, + Session const& windowApp, + Session const& cameraCtrl, Session const& shapeSpawn) { - OSP_SESSION_UNPACK_DATA(shapeSpawn, TESTAPP_SHAPE_SPAWN); - OSP_SESSION_UNPACK_TAGS(shapeSpawn, TESTAPP_SHAPE_SPAWN); - OSP_SESSION_UNPACK_TAGS(magnum, TESTAPP_APP_MAGNUM); - OSP_SESSION_UNPACK_DATA(simpleCamera, TESTAPP_CAMERA_CTRL); - OSP_SESSION_UNPACK_TAGS(simpleCamera, TESTAPP_CAMERA_CTRL); - + OSP_DECLARE_GET_DATA_IDS(shapeSpawn, TESTAPP_DATA_SHAPE_SPAWN); + OSP_DECLARE_GET_DATA_IDS(cameraCtrl, TESTAPP_DATA_CAMERA_CTRL); auto &rCamCtrl = top_get< ACtxCameraController > (topData, idCamCtrl); - Session thrower; - auto const [idBtnThrow] = thrower.acquire_data<1>(topData); + auto const tgWin = windowApp .get_pipelines(); + auto const tgCmCt = cameraCtrl.get_pipelines(); + auto const tgShSp = shapeSpawn.get_pipelines(); + + Session out; + auto const [idBtnThrow] = out.acquire_data<1>(topData); top_emplace< EButtonControlIndex > (topData, idBtnThrow, rCamCtrl.m_controls.button_subscribe("debug_throw")); - thrower.task() = rBuilder.task().assign({tgInputEvt, tgSpawnMod, tgCamCtrlReq}).data( - "Throw spheres when pressing space", - TopDataIds_t{ idCamCtrl, idSpawner, idBtnThrow}, - wrap_args([] (ACtxCameraController& rCamCtrl, SpawnerVec_t& rSpawner, EButtonControlIndex btnThrow) noexcept + rBuilder.task() + .name ("Throw spheres when pressing space") + .run_on ({tgWin.inputs(Run)}) + .sync_with ({tgCmCt.camCtrl(Ready), tgShSp.spawnRequest(Modify_)}) + .push_to (out.m_tasks) + .args ({ idCamCtrl, idSpawner, idBtnThrow }) + .func([] (ACtxCameraController& rCamCtrl, ACtxShapeSpawner& rSpawner, EButtonControlIndex btnThrow) noexcept { // Throw a sphere when the throw button is pressed if (rCamCtrl.m_controls.button_held(btnThrow)) @@ -225,50 +244,54 @@ Session setup_thrower( Matrix4 const &camTf = rCamCtrl.m_transform; float const speed = 120; float const dist = 8.0f; - rSpawner.emplace_back(SpawnShape{ + rSpawner.m_spawnRequest.push_back({ .m_position = camTf.translation() - camTf.backward() * dist, .m_velocity = -camTf.backward() * speed, .m_size = Vector3{1.0f}, .m_mass = 1.0f, .m_shape = EShape::Sphere }); - return osp::TopTaskStatus::Success; } - return osp::TopTaskStatus::Success; - })); + }); - return thrower; + return out; } Session setup_droppers( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& scnCommon, + Session const& scene, + Session const& commonScene, Session const& shapeSpawn) { - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_DATA(shapeSpawn, TESTAPP_SHAPE_SPAWN); - OSP_SESSION_UNPACK_TAGS(shapeSpawn, TESTAPP_SHAPE_SPAWN); + OSP_DECLARE_GET_DATA_IDS(scene, TESTAPP_DATA_SCENE); + OSP_DECLARE_GET_DATA_IDS(commonScene, TESTAPP_DATA_COMMON_SCENE); + OSP_DECLARE_GET_DATA_IDS(shapeSpawn, TESTAPP_DATA_SHAPE_SPAWN); - Session droppers; - auto const [idSpawnTimerA, idSpawnTimerB] = droppers.acquire_data<2>(topData); + auto const tgScn = scene .get_pipelines(); + auto const tgShSp = shapeSpawn .get_pipelines(); + + Session out; + auto const [idSpawnTimerA, idSpawnTimerB] = out.acquire_data<2>(topData); top_emplace< float > (topData, idSpawnTimerA, 0.0f); top_emplace< float > (topData, idSpawnTimerB, 0.0f); - droppers.task() = rBuilder.task().assign({tgTimeEvt, tgSpawnMod}).data( - "Spawn blocks every 2 seconds", - TopDataIds_t{ idSpawner, idSpawnTimerA, idDeltaTimeIn }, - wrap_args([] (SpawnerVec_t& rSpawner, float& rSpawnTimer, float const deltaTimeIn) noexcept + rBuilder.task() + .name ("Spawn blocks every 2 seconds") + .run_on ({tgScn.update(Run)}) + .sync_with ({tgShSp.spawnRequest(Modify_)}) + .push_to (out.m_tasks) + .args({ idSpawner, idSpawnTimerA, idDeltaTimeIn }) + .func([] (ACtxShapeSpawner& rSpawner, float& rSpawnTimer, float const deltaTimeIn) noexcept + { rSpawnTimer += deltaTimeIn; if (rSpawnTimer >= 2.0f) { rSpawnTimer -= 2.0f; - rSpawner.emplace_back(SpawnShape{ + rSpawner.m_spawnRequest.push_back({ .m_position = {10.0f, 0.0f, 30.0f}, .m_velocity = {0.0f, 0.0f, 0.0f}, .m_size = {2.0f, 2.0f, 1.0f}, @@ -276,19 +299,22 @@ Session setup_droppers( .m_shape = EShape::Box }); } - })); + }); - droppers.task() = rBuilder.task().assign({tgTimeEvt, tgSpawnMod}).data( - "Spawn cylinders every 1 seconds", - TopDataIds_t{ idSpawner, idSpawnTimerB, idDeltaTimeIn }, - wrap_args([] (SpawnerVec_t& rSpawner, float& rSpawnTimer, float const deltaTimeIn) noexcept + rBuilder.task() + .name ("Spawn cylinders every 1 second") + .run_on ({tgScn.update(Run)}) + .sync_with ({tgShSp.spawnRequest(Modify_)}) + .push_to (out.m_tasks) + .args({ idSpawner, idSpawnTimerB, idDeltaTimeIn }) + .func([] (ACtxShapeSpawner& rSpawner, float& rSpawnTimer, float const deltaTimeIn) noexcept { rSpawnTimer += deltaTimeIn; if (rSpawnTimer >= 1.0f) { rSpawnTimer -= 1.0f; - rSpawner.emplace_back(SpawnShape{ + rSpawner.m_spawnRequest.push_back({ .m_position = {-10.0f, 0.0, 30.0f}, .m_velocity = {0.0f, 0.0f, 0.0f}, .m_size = {2.0f, 2.0f, 1.0f}, @@ -296,9 +322,114 @@ Session setup_droppers( .m_shape = EShape::Cylinder }); } - })); + }); - return droppers; + return out; } +Session setup_bounds( + TopTaskBuilder& rBuilder, + ArrayView const topData, + Session const& scene, + Session const& commonScene, + Session const& shapeSpawn) +{ + using ActiveEntVec_t = std::vector; + + OSP_DECLARE_GET_DATA_IDS(commonScene, TESTAPP_DATA_COMMON_SCENE); + OSP_DECLARE_GET_DATA_IDS(shapeSpawn, TESTAPP_DATA_SHAPE_SPAWN); + auto const tgScn = scene .get_pipelines(); + auto const tgCS = commonScene .get_pipelines(); + auto const tgShSp = shapeSpawn .get_pipelines(); + + Session out; + OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_BOUNDS); + auto const tgBnds = out.create_pipelines(rBuilder); + + rBuilder.pipeline(tgBnds.boundsSet) .parent(tgScn.update); + rBuilder.pipeline(tgBnds.outOfBounds) .parent(tgScn.update); + + top_emplace< ActiveEntSet_t > (topData, idBounds); + top_emplace< ActiveEntVec_t > (topData, idOutOfBounds); + + rBuilder.task() + .name ("Check for out-of-bounds entities") + .run_on ({tgScn.update(Run)}) + .sync_with ({tgCS.transform(Ready), tgBnds.boundsSet(Ready), tgBnds.outOfBounds(Modify__)}) + .push_to (out.m_tasks) + .args ({ idBasic, idBounds, idOutOfBounds }) + .func([] (ACtxBasic const& rBasic, ActiveEntSet_t const& rBounds, ActiveEntVec_t& rOutOfBounds) noexcept + { + for (std::size_t const ent : rBounds.ones()) + { + ACompTransform const &entTf = rBasic.m_transform.get(ActiveEnt(ent)); + if (entTf.m_transform.translation().z() < -10) + { + rOutOfBounds.push_back(ActiveEnt(ent)); + } + } + }); + + rBuilder.task() + .name ("Queue-Delete out-of-bounds entities") + .run_on ({tgBnds.outOfBounds(UseOrRun_)}) + .sync_with ({tgCS.activeEntDelete(Modify_), tgCS.hierarchy(Delete)}) + .push_to (out.m_tasks) + .args ({ idBasic, idActiveEntDel, idOutOfBounds }) + .func([] (ACtxBasic& rBasic, ActiveEntVec_t& rActiveEntDel, ActiveEntVec_t& rOutOfBounds) noexcept + { + SysSceneGraph::queue_delete_entities(rBasic.m_scnGraph, rActiveEntDel, rOutOfBounds.begin(), rOutOfBounds.end()); + }); + + rBuilder.task() + .name ("Clear out-of-bounds vector once we're done with it") + .run_on ({tgBnds.outOfBounds(Clear_)}) + .push_to (out.m_tasks) + .args ({ idOutOfBounds }) + .func([] (ActiveEntVec_t& rOutOfBounds) noexcept + { + rOutOfBounds.clear(); + }); + + rBuilder.task() + .name ("Add bounds to spawned shapes") + .run_on ({tgShSp.spawnRequest(UseOrRun)}) + .sync_with ({tgShSp.spawnedEnts(UseOrRun), tgBnds.boundsSet(Modify)}) + .push_to (out.m_tasks) + .args ({ idBasic, idSpawner, idBounds }) + .func([] (ACtxBasic& rBasic, ACtxShapeSpawner& rSpawner, ActiveEntSet_t& rBounds) noexcept + { + rBounds.ints().resize(rBasic.m_activeIds.vec().capacity()); + + for (std::size_t i = 0; i < rSpawner.m_spawnRequest.size(); ++i) + { + SpawnShape const &spawn = rSpawner.m_spawnRequest[i]; + if (spawn.m_mass == 0) + { + continue; + } + + ActiveEnt const root = rSpawner.m_ents[i * 2]; + + rBounds.set(std::size_t(root)); + } + }); + + rBuilder.task() + .name ("Delete bounds components") + .run_on ({tgCS.activeEntDelete(UseOrRun)}) + .sync_with ({tgBnds.boundsSet(Delete)}) + .push_to (out.m_tasks) + .args ({ idActiveEntDel, idBounds }) + .func([] (ActiveEntVec_t const& rActiveEntDel, ActiveEntSet_t& rBounds) noexcept + { + for (osp::active::ActiveEnt const ent : rActiveEntDel) + { + rBounds.reset(std::size_t(ent)); + } + }); + + return out; } + +} // namespace testapp::scenes diff --git a/src/test_application/activescenes/scene_misc.h b/src/test_application/activescenes/scene_misc.h index 40901a61..e86e8ae1 100644 --- a/src/test_application/activescenes/scene_misc.h +++ b/src/test_application/activescenes/scene_misc.h @@ -26,58 +26,72 @@ #include "scenarios.h" +#include + namespace testapp::scenes { +void create_materials( + osp::ArrayView topData, + osp::Session const& commonScene, + int count); + void add_floor( osp::ArrayView topData, - osp::Session const& scnCommon, - osp::Session const& material, + osp::Session const& application, + osp::Session const& commonScene, osp::Session const& shapeSpawn, - osp::TopDataId idResources, + osp::active::MaterialId material, osp::PkgId pkg); /** * @brief Create CameraController connected to an app's UserInputHandler */ osp::Session setup_camera_ctrl( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& app, + osp::Session const& windowApp, osp::Session const& scnRender); /** * @brief Adds free cam controls to a CameraController */ osp::Session setup_camera_free( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& app, - osp::Session const& scnCommon, + osp::Session const& windowApp, + osp::Session const& scene, osp::Session const& camera); /** * @brief Throws spheres when pressing space */ osp::Session setup_thrower( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& magnum, - osp::Session const& renderer, - osp::Session const& simpleCamera, + osp::Session const& windowApp, + osp::Session const& cameraCtrl, osp::Session const& shapeSpawn); /** * @brief Spawn blocks every 2 seconds and spheres every 1 second */ osp::Session setup_droppers( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& scene, + osp::Session const& commonScene, osp::Session const& shapeSpawn); +/** + * @brief Entity set to delete entities under Z = -10, added to spawned shapes + */ +osp::Session setup_bounds( + osp::TopTaskBuilder& rBuilder, + osp::ArrayView topData, + osp::Session const& scene, + osp::Session const& commonScene, + osp::Session const& shapeSpawn); + + } diff --git a/src/test_application/activescenes/scene_newton.cpp b/src/test_application/activescenes/scene_newton.cpp index 2a4c4d2a..2c89fbcc 100644 --- a/src/test_application/activescenes/scene_newton.cpp +++ b/src/test_application/activescenes/scene_newton.cpp @@ -58,82 +58,88 @@ namespace testapp::scenes Session setup_newton( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& scnCommon, + osp::Session const& scene, + Session const& commonScene, Session const& physics) { - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_DATA(physics, TESTAPP_PHYSICS); - OSP_SESSION_UNPACK_TAGS(physics, TESTAPP_PHYSICS); + OSP_DECLARE_GET_DATA_IDS(scene, TESTAPP_DATA_SCENE); + OSP_DECLARE_GET_DATA_IDS(commonScene, TESTAPP_DATA_COMMON_SCENE); + OSP_DECLARE_GET_DATA_IDS(physics, TESTAPP_DATA_PHYSICS); - Session newton; - OSP_SESSION_ACQUIRE_DATA(newton, topData, TESTAPP_NEWTON); - OSP_SESSION_ACQUIRE_TAGS(newton, rTags, TESTAPP_NEWTON); + auto const tgScn = scene .get_pipelines(); + auto const tgCS = commonScene .get_pipelines(); + auto const tgPhy = physics .get_pipelines(); - rBuilder.tag(tgNwtBodyDel).depend_on({tgNwtBodyPrv}); - rBuilder.tag(tgNwtBodyMod).depend_on({tgNwtBodyPrv, tgNwtBodyDel}); - rBuilder.tag(tgNwtBodyReq).depend_on({tgNwtBodyPrv, tgNwtBodyDel, tgNwtBodyMod}); - rBuilder.tag(tgNwtBodyClr).depend_on({tgNwtBodyPrv, tgNwtBodyDel, tgNwtBodyMod, tgNwtBodyReq}); + Session out; + OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_NEWTON); + auto const tgNwt = out.create_pipelines(rBuilder); + + rBuilder.pipeline(tgNwt.nwtBody).parent(tgScn.update); top_emplace< ACtxNwtWorld >(topData, idNwt, 2); using ospnewton::SysNewton; - newton.task() = rBuilder.task().assign({tgSceneEvt, tgDelTotalReq, tgNwtBodyDel}).data( - "Delete Newton components", - TopDataIds_t{ idNwt, idDelTotal}, - wrap_args([] (ACtxNwtWorld& rNwt, EntVector_t const& rDelTotal) noexcept + rBuilder.task() + .name ("Delete Newton components") + .run_on ({tgCS.activeEntDelete(UseOrRun)}) + .sync_with ({tgNwt.nwtBody(Delete)}) + .push_to (out.m_tasks) + .args({ idNwt, idActiveEntDel }) + .func([] (ACtxNwtWorld& rNwt, ActiveEntVec_t const& rActiveEntDel) noexcept { - SysNewton::update_delete (rNwt, std::cbegin(rDelTotal), std::cend(rDelTotal)); - })); - - newton.task() = rBuilder.task().assign({tgTimeEvt, tgPhysPrv, tgNwtBodyPrv, tgPhysTransformMod, tgTransformMod}).data( - "Update Newton world", - TopDataIds_t{ idBasic, idPhys, idNwt, idDeltaTimeIn }, - wrap_args([] (ACtxBasic& rBasic, ACtxPhysics& rPhys, ACtxNwtWorld& rNwt, float const deltaTimeIn) noexcept + SysNewton::update_delete (rNwt, rActiveEntDel.cbegin(), rActiveEntDel.cend()); + }); + + rBuilder.task() + .name ("Update Newton world") + .run_on ({tgScn.update(Run)}) + .sync_with ({tgNwt.nwtBody(Prev), tgCS.hierarchy(Prev), tgPhy.physBody(Prev), tgPhy.physUpdate(Run), tgCS.transform(Prev)}) + .push_to (out.m_tasks) + .args({ idBasic, idPhys, idNwt, idDeltaTimeIn }) + .func([] (ACtxBasic& rBasic, ACtxPhysics& rPhys, ACtxNwtWorld& rNwt, float const deltaTimeIn, WorkerContext ctx) noexcept { SysNewton::update_world(rPhys, rNwt, deltaTimeIn, rBasic.m_scnGraph, rBasic.m_transform); - })); + }); top_emplace< ACtxNwtWorld >(topData, idNwt, 2); - return newton; + return out; } + + osp::Session setup_newton_factors( - Builder_t& rBuilder, - ArrayView topData, - Tags& rTags) + TopTaskBuilder& rBuilder, + ArrayView topData) { - Session nwtFactors; - OSP_SESSION_ACQUIRE_DATA(nwtFactors, topData, TESTAPP_NEWTON_FORCES); + Session out; + OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_NEWTON_FORCES); auto &rFactors = top_emplace(topData, idNwtFactors); std::fill(rFactors.begin(), rFactors.end(), 0); - return nwtFactors; + return out; } osp::Session setup_newton_force_accel( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView topData, - Tags& rTags, Session const& newton, Session const& nwtFactors, Vector3 accel) { using UserData_t = ACtxNwtWorld::ForceFactorFunc::UserData_t; - OSP_SESSION_UNPACK_DATA(newton, TESTAPP_NEWTON); - OSP_SESSION_UNPACK_DATA(nwtFactors, TESTAPP_NEWTON_FORCES); + OSP_DECLARE_GET_DATA_IDS(newton, TESTAPP_DATA_NEWTON); + OSP_DECLARE_GET_DATA_IDS(nwtFactors, TESTAPP_DATA_NEWTON_FORCES); auto &rNwt = top_get(topData, idNwt); Session nwtAccel; - OSP_SESSION_ACQUIRE_DATA(nwtAccel, topData, TESTAPP_NEWTON_ACCEL); + OSP_DECLARE_CREATE_DATA_IDS(nwtAccel, topData, TESTAPP_DATA_NEWTON_ACCEL); auto &rAccel = top_emplace(topData, idAcceleration, accel); @@ -164,38 +170,40 @@ osp::Session setup_newton_force_accel( Session setup_shape_spawn_newton( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& scnCommon, + Session const& commonScene, Session const& physics, Session const& shapeSpawn, Session const& newton, Session const& nwtFactors) { - Session shapeSpawnNwt; - - - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_DATA(physics, TESTAPP_PHYSICS); - OSP_SESSION_UNPACK_TAGS(physics, TESTAPP_PHYSICS); - OSP_SESSION_UNPACK_DATA(shapeSpawn, TESTAPP_SHAPE_SPAWN); - OSP_SESSION_UNPACK_TAGS(shapeSpawn, TESTAPP_SHAPE_SPAWN); - OSP_SESSION_UNPACK_DATA(newton, TESTAPP_NEWTON); - OSP_SESSION_UNPACK_TAGS(newton, TESTAPP_NEWTON); - OSP_SESSION_UNPACK_DATA(nwtFactors, TESTAPP_NEWTON_FORCES); - - shapeSpawnNwt.task() = rBuilder.task().assign({tgSceneEvt, tgSpawnReq, tgSpawnEntReq, tgNwtBodyMod}).data( - "Add physics to spawned shapes", - TopDataIds_t{ idActiveIds, idSpawner, idSpawnerEnts, idPhys, idNwt, idNwtFactors }, - wrap_args([] (ActiveReg_t const &rActiveIds, SpawnerVec_t& rSpawner, EntVector_t& rSpawnerEnts, ACtxPhysics& rPhys, ACtxNwtWorld& rNwt, ForceFactors_t nwtFactors) noexcept + + OSP_DECLARE_GET_DATA_IDS(commonScene, TESTAPP_DATA_COMMON_SCENE); + OSP_DECLARE_GET_DATA_IDS(physics, TESTAPP_DATA_PHYSICS); + OSP_DECLARE_GET_DATA_IDS(shapeSpawn, TESTAPP_DATA_SHAPE_SPAWN); + OSP_DECLARE_GET_DATA_IDS(newton, TESTAPP_DATA_NEWTON); + OSP_DECLARE_GET_DATA_IDS(nwtFactors, TESTAPP_DATA_NEWTON_FORCES); + + auto const tgPhy = physics .get_pipelines(); + auto const tgShSp = shapeSpawn .get_pipelines(); + auto const tgNwt = newton .get_pipelines(); + + Session out; + + rBuilder.task() + .name ("Add Newton physics to spawned shapes") + .run_on ({tgShSp.spawnRequest(UseOrRun)}) + .sync_with ({tgShSp.spawnedEnts(UseOrRun), tgNwt.nwtBody(New), tgPhy.physUpdate(Done)}) + .push_to (out.m_tasks) + .args({ idBasic, idSpawner, idPhys, idNwt, idNwtFactors }) + .func([] (ACtxBasic const &rBasic, ACtxShapeSpawner& rSpawner, ACtxPhysics& rPhys, ACtxNwtWorld& rNwt, ForceFactors_t nwtFactors) noexcept { - for (std::size_t i = 0; i < rSpawner.size(); ++i) + for (std::size_t i = 0; i < rSpawner.m_spawnRequest.size(); ++i) { - SpawnShape const &spawn = rSpawner[i]; - ActiveEnt const root = rSpawnerEnts[i * 2]; - ActiveEnt const child = rSpawnerEnts[i * 2 + 1]; + SpawnShape const &spawn = rSpawner.m_spawnRequest[i]; + ActiveEnt const root = rSpawner.m_ents[i * 2]; + ActiveEnt const child = rSpawner.m_ents[i * 2 + 1]; NwtColliderPtr_t pCollision{ SysNewton::create_primative(rNwt, spawn.m_shape) }; SysNewton::orient_collision(pCollision.get(), spawn.m_shape, {0.0f, 0.0f, 0.0f}, Matrix3{}, spawn.m_size); @@ -219,11 +227,14 @@ Session setup_shape_spawn_newton( NewtonBodySetTransformCallback(pBody, &SysNewton::cb_set_transform); SysNewton::set_userdata_bodyid(pBody, bodyId); } - })); + }); - return shapeSpawnNwt; + return out; } +#if 0 + + void compound_collect_recurse( ACtxPhysics const& rCtxPhys, ACtxNwtWorld& rCtxWorld, @@ -271,11 +282,12 @@ void compound_collect_recurse( } + + Session setup_vehicle_spawn_newton( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& scnCommon, + Session const& commonScene, Session const& physics, Session const& prefabs, Session const& parts, @@ -283,8 +295,8 @@ Session setup_vehicle_spawn_newton( Session const& newton, TopDataId const idResources) { - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(commonScene, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); OSP_SESSION_UNPACK_DATA(physics, TESTAPP_PHYSICS); OSP_SESSION_UNPACK_TAGS(physics, TESTAPP_PHYSICS); OSP_SESSION_UNPACK_DATA(prefabs, TESTAPP_PREFABS); @@ -303,10 +315,15 @@ Session setup_vehicle_spawn_newton( rBuilder.tag(tgNwtVhWeldEntReq) .depend_on({tgNwtVhWeldEntMod}); rBuilder.tag(tgNwtVhHierReq) .depend_on({tgNwtVhHierMod}); + rBuilder.task() + .name ("aaa") + .depends_on ({}) + .fulfills ({}) + .push_to (out.m_tasks) vehicleSpawnNwt.task() = rBuilder.task().assign({tgSceneEvt, tgEntNew, tgNwtVhWeldEntMod}).data( "Create entity for each rigid group", - TopDataIds_t{ idActiveIds, idVehicleSpawn, idScnParts }, - wrap_args([] (ActiveReg_t& rActiveIds, ACtxVehicleSpawn& rVehicleSpawn, ACtxParts& rScnParts) noexcept + .args({ idActiveIds, idVehicleSpawn, idScnParts }) + .func([] (ActiveReg_t& rActiveIds, ACtxVehicleSpawn& rVehicleSpawn, ACtxParts& rScnParts) noexcept { if (rVehicleSpawn.new_vehicle_count() == 0) { @@ -325,10 +342,15 @@ Session setup_vehicle_spawn_newton( } })); + rBuilder.task() + .name ("aaa") + .depends_on ({}) + .fulfills ({}) + .push_to (out.m_tasks) vehicleSpawnNwt.task() = rBuilder.task().assign({tgSceneEvt, tgVsBasicInReq, tgVsWeldReq, tgNwtVhWeldEntReq, tgPrefabEntReq, tgNwtVhHierMod, tgPfParentHierMod, tgHierMod, tgTransformNew}).data( "Add vehicle entities to Scene Graph", - TopDataIds_t{ idBasic, idActiveIds, idVehicleSpawn, idScnParts, idPrefabInit, idResources }, - wrap_args([] (ACtxBasic& rBasic, ActiveReg_t const& rActiveIds, ACtxVehicleSpawn const& rVehicleSpawn, ACtxParts& rScnParts, ACtxPrefabInit& rPrefabInit, Resources& rResources) noexcept + .args({ idBasic, idActiveIds, idVehicleSpawn, idScnParts, idPrefabInit, idResources }) + .func([] (ACtxBasic& rBasic, ActiveReg_t const& rActiveIds, ACtxVehicleSpawn const& rVehicleSpawn, ACtxParts& rScnParts, ACtxPrefabInit& rPrefabInit, Resources& rResources) noexcept { // ActiveEnts created for welds + ActiveEnts created for vehicle prefabs std::size_t const totalEnts = rVehicleSpawn.m_newWeldToEnt.size() + rPrefabInit.m_newEnts.size(); @@ -381,10 +403,15 @@ Session setup_vehicle_spawn_newton( } })); + rBuilder.task() + .name ("aaa") + .depends_on ({}) + .fulfills ({}) + .push_to (out.m_tasks) vehicleSpawnNwt.task() = rBuilder.task().assign({tgSceneEvt, tgVsBasicInReq, tgVsWeldReq, tgNwtVhWeldEntReq, tgNwtVhHierReq, tgPfParentHierReq, tgNwtBodyMod}).data( "Add Newton physics to rigid group entities", - TopDataIds_t{ idActiveIds, idBasic, idPhys, idNwt, idVehicleSpawn, idScnParts }, - wrap_args([] (ActiveReg_t const &rActiveIds, ACtxBasic& rBasic, ACtxPhysics& rPhys, ACtxNwtWorld& rNwt, ACtxVehicleSpawn const& rVehicleSpawn, ACtxParts const& rScnParts) noexcept + .args({ idActiveIds, idBasic, idPhys, idNwt, idVehicleSpawn, idScnParts }) + .func([] (ActiveReg_t const &rActiveIds, ACtxBasic& rBasic, ACtxPhysics& rPhys, ACtxNwtWorld& rNwt, ACtxVehicleSpawn const& rVehicleSpawn, ACtxParts const& rScnParts) noexcept { if (rVehicleSpawn.new_vehicle_count() == 0) { @@ -579,11 +606,55 @@ static void assign_rockets( rTemp.clear(); } +// ACtxNwtWorld::ForceFactorFunc::Func_t +static void rocket_thrust_force(NewtonBody const* pBody, BodyId const body, ACtxNwtWorld const& rNwt, ACtxNwtWorld::ForceFactorFunc::UserData_t data, Vector3& rForce, Vector3& rTorque) noexcept +{ + auto const& rRocketsNwt = *reinterpret_cast (data[0]); + auto const& rMachines = *reinterpret_cast (data[1]); + auto const& rSigValFloat = *reinterpret_cast const*> (data[2]); + + auto &rBodyRockets = rRocketsNwt.m_bodyRockets[body]; + + if (rBodyRockets.size() == 0) + { + return; + } + + std::array nwtRot; // quaternion xyzw + NewtonBodyGetRotation(pBody, nwtRot.data()); + Quaternion const rot{{nwtRot[0], nwtRot[1], nwtRot[2]}, nwtRot[3]}; + + Vector3 com; + NewtonBodyGetCentreOfMass(pBody, com.data()); + + for (BodyRocket const& bodyRocket : rBodyRockets) + { + float const throttle = std::clamp(rSigValFloat[bodyRocket.m_throttleIn], 0.0f, 1.0f); + float const multiplier = rSigValFloat[bodyRocket.m_multiplierIn]; + + float const thrustMag = throttle * multiplier; + + if (thrustMag == 0.0f) + { + continue; + } + + Vector3 const offsetRel = rot.transformVector(bodyRocket.m_offset - com); + + Vector3 const direction = (rot * bodyRocket.m_rotation).transformVector(adera::gc_rocketForward); + + Vector3 const thrustForce = direction * thrustMag; + Vector3 const thrustTorque = Magnum::Math::cross(offsetRel, thrustForce); + + rForce += thrustForce; + rTorque += thrustTorque; + } +} + Session setup_rocket_thrust_newton( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& scnCommon, + Session const& commonScene, Session const& physics, Session const& prefabs, Session const& parts, @@ -591,8 +662,8 @@ Session setup_rocket_thrust_newton( Session const& newton, Session const& nwtFactors) { - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(commonScene, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); OSP_SESSION_UNPACK_DATA(physics, TESTAPP_PHYSICS); OSP_SESSION_UNPACK_TAGS(physics, TESTAPP_PHYSICS); OSP_SESSION_UNPACK_DATA(prefabs, TESTAPP_PREFABS); @@ -611,11 +682,15 @@ Session setup_rocket_thrust_newton( auto &rRocketsNwt = top_emplace< ACtxRocketsNwt >(topData, idRocketsNwt); - + rBuilder.task() + .name ("aaa") + .depends_on ({}) + .fulfills ({}) + .push_to (out.m_tasks) rocketNwt.task() = rBuilder.task().assign({tgSceneEvt, tgLinkReq, tgWeldReq, tgNwtBodyReq}).data( "Assign rockets to Newton bodies", - TopDataIds_t{ idActiveIds, idBasic, idPhys, idNwt, idScnParts, idRocketsNwt, idNwtFactors}, - wrap_args([] (ActiveReg_t const &rActiveIds, ACtxBasic& rBasic, ACtxPhysics& rPhys, ACtxNwtWorld& rNwt, ACtxParts const& rScnParts, ACtxRocketsNwt& rRocketsNwt, ForceFactors_t const& rNwtFactors) noexcept + .args({ idActiveIds, idBasic, idPhys, idNwt, idScnParts, idRocketsNwt, idNwtFactors }) + .func([] (ActiveReg_t const &rActiveIds, ACtxBasic& rBasic, ACtxPhysics& rPhys, ACtxNwtWorld& rNwt, ACtxParts const& rScnParts, ACtxRocketsNwt& rRocketsNwt, ForceFactors_t const& rNwtFactors) noexcept { using adera::gc_mtMagicRocket; @@ -637,52 +712,10 @@ Session setup_rocket_thrust_newton( auto &rSigValFloat = top_get< SignalValues_t > (topData, idSigValFloat); Machines &rMachines = rScnParts.m_machines; - using UserData_t = ACtxNwtWorld::ForceFactorFunc::UserData_t; + ACtxNwtWorld::ForceFactorFunc const factor { - .m_func = [] (NewtonBody const* pBody, BodyId const body, ACtxNwtWorld const& rNwt, UserData_t data, Vector3& rForce, Vector3& rTorque) noexcept - { - auto const& rRocketsNwt = *reinterpret_cast (data[0]); - auto const& rMachines = *reinterpret_cast (data[1]); - auto const& rSigValFloat = *reinterpret_cast const*> (data[2]); - - auto &rBodyRockets = rRocketsNwt.m_bodyRockets[body]; - - if (rBodyRockets.size() == 0) - { - return; - } - - std::array nwtRot; // quaternion xyzw - NewtonBodyGetRotation(pBody, nwtRot.data()); - Quaternion const rot{{nwtRot[0], nwtRot[1], nwtRot[2]}, nwtRot[3]}; - - Vector3 com; - NewtonBodyGetCentreOfMass(pBody, com.data()); - - for (BodyRocket const& bodyRocket : rBodyRockets) - { - float const throttle = std::clamp(rSigValFloat[bodyRocket.m_throttleIn], 0.0f, 1.0f); - float const multiplier = rSigValFloat[bodyRocket.m_multiplierIn]; - - float const thrustMag = throttle * multiplier; - - if (thrustMag == 0.0f) - { - continue; - } - - Vector3 const offsetRel = rot.transformVector(bodyRocket.m_offset - com); - - Vector3 const direction = (rot * bodyRocket.m_rotation).transformVector(adera::gc_rocketForward); - - Vector3 const thrustForce = direction * thrustMag; - Vector3 const thrustTorque = Magnum::Math::cross(offsetRel, thrustForce); - - rForce += thrustForce; - rTorque += thrustTorque; - } - }, + .m_func = &rocket_thrust_force, .m_userData = { &rRocketsNwt, &rMachines, &rSigValFloat } }; @@ -696,7 +729,7 @@ Session setup_rocket_thrust_newton( return rocketNwt; } - +#endif } // namespace testapp::scenes diff --git a/src/test_application/activescenes/scene_newton.h b/src/test_application/activescenes/scene_newton.h index 5808565f..55738f29 100644 --- a/src/test_application/activescenes/scene_newton.h +++ b/src/test_application/activescenes/scene_newton.h @@ -28,7 +28,6 @@ #include #include -#include #include #include #include @@ -44,10 +43,10 @@ namespace testapp::scenes * @brief Newton Dynamics physics integration */ osp::Session setup_newton( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& scene, + osp::Session const& commonScene, osp::Session const& physics); /** @@ -57,17 +56,15 @@ osp::Session setup_newton( * functions contribute to its force and torque */ osp::Session setup_newton_factors( - Builder_t& rBuilder, - osp::ArrayView topData, - osp::Tags& rTags); + osp::TopTaskBuilder& rBuilder, + osp::ArrayView topData); /** * @brief Setup constant acceleration force, add to a force factor bitset */ osp::Session setup_newton_force_accel( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, osp::Session const& newton, osp::Session const& nwtFactors, osp::Vector3 accel); @@ -76,10 +73,9 @@ osp::Session setup_newton_force_accel( * @brief Support for Shape Spawner physics using Newton Dynamics */ osp::Session setup_shape_spawn_newton( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& commonScene, osp::Session const& physics, osp::Session const& shapeSpawn, osp::Session const& newton, @@ -89,10 +85,9 @@ osp::Session setup_shape_spawn_newton( * @brief Support for Vehicle physics using Newton Dynamics */ osp::Session setup_vehicle_spawn_newton( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& commonScene, osp::Session const& physics, osp::Session const& prefabs, osp::Session const& parts, @@ -104,10 +99,9 @@ osp::Session setup_vehicle_spawn_newton( * @brief Add thrust forces to Magic Rockets from setup_mach_rocket */ osp::Session setup_rocket_thrust_newton( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& commonScene, osp::Session const& physics, osp::Session const& prefabs, osp::Session const& parts, diff --git a/src/test_application/activescenes/scene_physics.cpp b/src/test_application/activescenes/scene_physics.cpp index af0e7477..814e4d8e 100644 --- a/src/test_application/activescenes/scene_physics.cpp +++ b/src/test_application/activescenes/scene_physics.cpp @@ -48,155 +48,161 @@ using Corrade::Containers::arrayView; namespace testapp::scenes { + + Session setup_physics( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& scnCommon) + Session const& scene, + Session const& commonScene) { - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); + OSP_DECLARE_GET_DATA_IDS(commonScene, TESTAPP_DATA_COMMON_SCENE); + auto const tgScn = scene .get_pipelines(); + auto const tgCS = commonScene.get_pipelines(); - Session physics; - OSP_SESSION_ACQUIRE_DATA(physics, topData, TESTAPP_PHYSICS); - OSP_SESSION_ACQUIRE_TAGS(physics, rTags, TESTAPP_PHYSICS); + Session out; + OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_PHYSICS); + auto const tgPhy = out.create_pipelines(rBuilder); + rBuilder.pipeline(tgPhy.physBody) .parent(tgScn.update); + rBuilder.pipeline(tgPhy.physUpdate).parent(tgScn.update); top_emplace< ACtxPhysics > (topData, idPhys); - // 'Per-thread' inputs fed into the physics engine. Only one here for now - //top_emplace< ACtxPhysInputs >(topData, idPhysIn); - - rBuilder.tag(tgPhysTransformReq).depend_on({tgPhysTransformMod}); - rBuilder.tag(tgPhysDel) .depend_on({tgPhysPrv}); - rBuilder.tag(tgPhysMod) .depend_on({tgPhysPrv, tgPhysDel}); - rBuilder.tag(tgPhysReq) .depend_on({tgPhysPrv, tgPhysDel, tgPhysMod}); - - physics.task() = rBuilder.task().assign({tgSceneEvt, tgDelTotalReq, tgPhysDel}).data( - "Delete Physics components", - TopDataIds_t{ idPhys, idDelTotal}, - wrap_args([] (ACtxPhysics& rPhys, EntVector_t const& rDelTotal) noexcept + rBuilder.task() + .name ("Delete Physics components") + .run_on ({tgCS.activeEntDelete(UseOrRun)}) + .sync_with ({tgPhy.physBody(Delete)}) + .push_to (out.m_tasks) + .args ({ idPhys, idActiveEntDel }) + .func([] (ACtxPhysics& rPhys, ActiveEntVec_t const& rActiveEntDel) noexcept { - auto const &delFirst = std::cbegin(rDelTotal); - auto const &delLast = std::cend(rDelTotal); + SysPhysics::update_delete_phys(rPhys, rActiveEntDel.cbegin(), rActiveEntDel.cend()); + }); - SysPhysics::update_delete_phys(rPhys, delFirst, delLast); - })); - - return physics; + return out; } Session setup_shape_spawn( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& scnCommon, + Session const& scene, + Session const& commonScene, Session const& physics, - Session const& material) + MaterialId const materialId) { - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_DATA(material, TESTAPP_MATERIAL); - OSP_SESSION_UNPACK_TAGS(material, TESTAPP_MATERIAL); - OSP_SESSION_UNPACK_DATA(physics, TESTAPP_PHYSICS); - OSP_SESSION_UNPACK_TAGS(physics, TESTAPP_PHYSICS); - - - Session shapeSpawn; - OSP_SESSION_ACQUIRE_DATA(shapeSpawn, topData, TESTAPP_SHAPE_SPAWN); - OSP_SESSION_ACQUIRE_TAGS(shapeSpawn, rTags, TESTAPP_SHAPE_SPAWN); - - top_emplace< SpawnerVec_t > (topData, idSpawner); - top_emplace< EntVector_t > (topData, idSpawnerEnts); - - rBuilder.tag(tgSpawnReq) .depend_on({tgSpawnMod}); - rBuilder.tag(tgSpawnClr) .depend_on({tgSpawnMod, tgSpawnReq}); - rBuilder.tag(tgSpawnEntReq) .depend_on({tgSpawnEntMod}); - - shapeSpawn.task() = rBuilder.task().assign({tgSceneEvt, tgSpawnReq, tgEntNew, tgSpawnEntMod}).data( - "Create entities for requested shapes to spawn", - TopDataIds_t{ idBasic, idActiveIds, idSpawner, idSpawnerEnts }, - wrap_args([] (ACtxBasic& rBasic, ActiveReg_t& rActiveIds, SpawnerVec_t& rSpawner, EntVector_t& rSpawnerEnts) noexcept + 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(); + auto const tgCS = commonScene .get_pipelines(); + auto const tgPhy = physics .get_pipelines(); + + Session out; + OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_SHAPE_SPAWN); + auto const tgShSp = out.create_pipelines(rBuilder); + + rBuilder.pipeline(tgShSp.spawnRequest) .parent(tgScn.update); + rBuilder.pipeline(tgShSp.spawnedEnts) .parent(tgScn.update); + + top_emplace< ACtxShapeSpawner > (topData, idSpawner, ACtxShapeSpawner{ .m_materialId = materialId }); + rBuilder.task() + .name ("Create entities for requested shapes to spawn") + .run_on ({tgShSp.spawnRequest(UseOrRun)}) + .sync_with ({tgCS.activeEnt(New), tgShSp.spawnedEnts(Resize)}) + .push_to (out.m_tasks) + .args ({ idBasic, idSpawner}) + .func([] (ACtxBasic& rBasic, ACtxShapeSpawner& rSpawner) noexcept { - if (rSpawner.size() == 0) - { - return; - } - - rSpawnerEnts.resize(rSpawner.size() * 2); - rActiveIds.create(std::begin(rSpawnerEnts), std::end(rSpawnerEnts)); - })); - - shapeSpawn.task() = rBuilder.task().assign({tgSceneEvt, tgSpawnReq, tgSpawnEntReq, tgTransformNew, tgHierNew}).data( - "Add hierarchy and transform to spawned shapes", - TopDataIds_t{ idBasic, idActiveIds, idSpawner, idSpawnerEnts }, - wrap_args([] (ACtxBasic& rBasic, ActiveReg_t& rActiveIds, SpawnerVec_t& rSpawner, EntVector_t& rSpawnerEnts) noexcept + //LGRN_ASSERTM(!rSpawner.m_spawnRequest.empty(), "spawnRequest Use_ shouldn't run if rSpawner.m_spawnRequest is empty!"); + + rSpawner.m_ents.resize(rSpawner.m_spawnRequest.size() * 2); + rBasic.m_activeIds.create(rSpawner.m_ents.begin(), rSpawner.m_ents.end()); + }); + + rBuilder.task() + .name ("Add hierarchy and transform to spawned shapes") + .run_on ({tgShSp.spawnRequest(UseOrRun)}) + .sync_with ({tgShSp.spawnedEnts(UseOrRun), tgCS.hierarchy(New), tgCS.transform(New)}) + .push_to (out.m_tasks) + .args ({ idBasic, idSpawner }) + .func([] (ACtxBasic& rBasic, ACtxShapeSpawner& rSpawner) noexcept { - if (rSpawner.size() == 0) - { - return; - } + rBasic.m_scnGraph.resize(rBasic.m_activeIds.capacity()); + SubtreeBuilder bldScnRoot = SysSceneGraph::add_descendants(rBasic.m_scnGraph, rSpawner.m_spawnRequest.size() * 2); - rBasic.m_scnGraph.resize(rActiveIds.capacity()); - SubtreeBuilder bldScnRoot = SysSceneGraph::add_descendants(rBasic.m_scnGraph, rSpawner.size() * 2); - - for (std::size_t i = 0; i < rSpawner.size(); ++i) + for (std::size_t i = 0; i < rSpawner.m_spawnRequest.size(); ++i) { - SpawnShape const &spawn = rSpawner[i]; - ActiveEnt const root = rSpawnerEnts[i * 2]; - ActiveEnt const child = rSpawnerEnts[i * 2 + 1]; + SpawnShape const &spawn = rSpawner.m_spawnRequest[i]; + ActiveEnt const root = rSpawner.m_ents[i * 2]; + ActiveEnt const child = rSpawner.m_ents[i * 2 + 1]; rBasic.m_transform.emplace(root, ACompTransform{osp::Matrix4::translation(spawn.m_position)}); rBasic.m_transform.emplace(child, ACompTransform{Matrix4::scaling(spawn.m_size)}); SubtreeBuilder bldRoot = bldScnRoot.add_child(root, 1); bldRoot.add_child(child); } - })); - - shapeSpawn.task() = rBuilder.task().assign({tgSceneEvt, tgSpawnReq, tgSpawnEntReq, tgMeshMod, tgDrawMod, tgMatMod}).data( - "Add mesh and material to spawned shapes", - TopDataIds_t{ idDrawing, idSpawner, idSpawnerEnts, idNMesh, idMatEnts, idMatDirty, idActiveIds}, - wrap_args([] (ACtxDrawing& rDrawing, SpawnerVec_t& rSpawner, EntVector_t& rSpawnerEnts, NamedMeshes& rNMesh, EntSet_t& rMatEnts, EntVector_t& rMatDirty, ActiveReg_t const& rActiveIds ) noexcept + }); + + rBuilder.task() + .name ("Add mesh and material to spawned shapes") + .run_on ({tgShSp.spawnRequest(UseOrRun)}) + .sync_with ({tgShSp.spawnedEnts(UseOrRun), + tgCS.entMesh(New), tgCS.material(New), tgCS.drawEnt(New), tgCS.drawEntResized(ModifyOrSignal), + tgCS.materialDirty(Modify_), tgCS.entMeshDirty(Modify_)}) + .push_to (out.m_tasks) + .args ({ idBasic, idDrawing, idSpawner, idNMesh }) + .func([] (ACtxBasic const& rBasic, ACtxDrawing& rDrawing, ACtxShapeSpawner& rSpawner, NamedMeshes& rNMesh) noexcept { - if (rSpawner.size() == 0) + Material &rMat = rDrawing.m_materials[rSpawner.m_materialId]; + + rDrawing.resize_active(rBasic.m_activeIds.capacity()); + + for (std::size_t i = 0; i < rSpawner.m_spawnRequest.size(); ++i) { - return; + ActiveEnt const child = rSpawner.m_ents[i * 2 + 1]; + rDrawing.m_activeToDraw[child] = rDrawing.m_drawIds.create(); } - rMatEnts.ints().resize(rActiveIds.vec().capacity()); - rDrawing.m_drawable.ints().resize(rActiveIds.vec().capacity()); - for (std::size_t i = 0; i < rSpawner.size(); ++i) + rDrawing.resize_draw(); + bitvector_resize(rMat.m_ents, rDrawing.m_drawIds.capacity()); + + for (std::size_t i = 0; i < rSpawner.m_spawnRequest.size(); ++i) { - SpawnShape const &spawn = rSpawner[i]; - ActiveEnt const root = rSpawnerEnts[i * 2]; - ActiveEnt const child = rSpawnerEnts[i * 2 + 1]; + SpawnShape const &spawn = rSpawner.m_spawnRequest[i]; + ActiveEnt const root = rSpawner.m_ents[i * 2]; + ActiveEnt const child = rSpawner.m_ents[i * 2 + 1]; + DrawEnt const drawEnt = rDrawing.m_activeToDraw[child]; - rDrawing.m_mesh.emplace( child, rDrawing.m_meshRefCounts.ref_add(rNMesh.m_shapeToMesh.at(spawn.m_shape)) ); - rDrawing.m_meshDirty.push_back(child); + rDrawing.m_needDrawTf.set(std::size_t(root)); + rDrawing.m_needDrawTf.set(std::size_t(child)); - rMatEnts.set(std::size_t(child)); - rMatDirty.push_back(child); + rDrawing.m_mesh[drawEnt] = rDrawing.m_meshRefCounts.ref_add(rNMesh.m_shapeToMesh.at(spawn.m_shape)); + rDrawing.m_meshDirty.push_back(drawEnt); - rDrawing.m_visible.emplace(child); - rDrawing.m_opaque.emplace(child); + rMat.m_ents.set(std::size_t(drawEnt)); + rMat.m_dirty.push_back(drawEnt); - rDrawing.m_drawable.set(std::size_t(root)); - rDrawing.m_drawable.set(std::size_t(child)); + rDrawing.m_visible.set(std::size_t(drawEnt)); + rDrawing.m_drawBasic[drawEnt].m_opaque = true; } - })); - - shapeSpawn.task() = rBuilder.task().assign({tgSceneEvt, tgSpawnReq, tgSpawnEntReq, tgPhysMod}).data( - "Add physics to spawned shapes", - TopDataIds_t{ idActiveIds, idSpawner, idSpawnerEnts, idPhys }, - wrap_args([] (ActiveReg_t const &rActiveIds, SpawnerVec_t& rSpawner, EntVector_t& rSpawnerEnts, ACtxPhysics& rPhys) noexcept + }); + + rBuilder.task() + .name ("Add physics to spawned shapes") + .run_on ({tgShSp.spawnRequest(UseOrRun)}) + .sync_with ({tgShSp.spawnedEnts(UseOrRun), tgPhy.physBody(Modify), tgPhy.physUpdate(Done)}) + .push_to (out.m_tasks) + .args ({ idBasic, idSpawner, idPhys }) + .func([] (ACtxBasic const& rBasic, ACtxShapeSpawner& rSpawner, ACtxPhysics& rPhys) noexcept { - rPhys.m_hasColliders.ints().resize(rActiveIds.vec().capacity()); - rPhys.m_shape.resize(rActiveIds.capacity()); - for (std::size_t i = 0; i < rSpawner.size(); ++i) + rPhys.m_hasColliders.ints().resize(rBasic.m_activeIds.vec().capacity()); + rPhys.m_shape.resize(rBasic.m_activeIds.capacity()); + + for (std::size_t i = 0; i < rSpawner.m_spawnRequest.size(); ++i) { - SpawnShape const &spawn = rSpawner[i]; - ActiveEnt const root = rSpawnerEnts[i * 2]; - ActiveEnt const child = rSpawnerEnts[i * 2 + 1]; + SpawnShape const &spawn = rSpawner.m_spawnRequest[i]; + ActiveEnt const root = rSpawner.m_ents[i * 2]; + ActiveEnt const child = rSpawner.m_ents[i * 2 + 1]; rPhys.m_hasColliders.set(std::size_t(root)); if (spawn.m_mass != 0.0f) @@ -211,31 +217,37 @@ Session setup_shape_spawn( rPhys.m_shape[std::size_t(child)] = spawn.m_shape; rPhys.m_colliderDirty.push_back(child); } - })); + }); + - shapeSpawn.task() = rBuilder.task().assign({tgSceneEvt, tgSpawnClr}).data( - "Clear Shape Spawning vector after use", - TopDataIds_t{ idSpawner}, - wrap_args([] (SpawnerVec_t& rSpawner) noexcept + rBuilder.task() + .name ("Clear Shape Spawning vector after use") + .run_on ({tgShSp.spawnRequest(Clear)}) + .push_to (out.m_tasks) + .args ({ idSpawner }) + .func([] (ACtxShapeSpawner& rSpawner) noexcept { - rSpawner.clear(); - })); + rSpawner.m_spawnRequest.clear(); + }); + - return shapeSpawn; + return out; } +#if 0 + Session setup_prefabs( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, Tags& rTags, - Session const& scnCommon, + Session const& commonScene, Session const& physics, Session const& material, TopDataId const idResources) { - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(commonScene, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); OSP_SESSION_UNPACK_DATA(physics, TESTAPP_PHYSICS); OSP_SESSION_UNPACK_TAGS(physics, TESTAPP_PHYSICS); @@ -295,7 +307,7 @@ Session setup_prefabs( //SysPrefabInit::init_hierarchy(rPrefabInit, rResources, rBasic.m_hierarchy); })); - prefabs.task() = rBuilder.task().assign({tgSceneEvt, tgPrefabReq, tgPrefabEntReq, tgTransformNew}).data( + prefabs.task() = rBuilder.task().assign({tgSceneEvt, tgPrefabReq, tgPrefabEntReq, PlransformNew}).data( "Init Prefab transforms", TopDataIds_t{ idPrefabInit, idResources, idBasic }, wrap_args([] (ACtxPrefabInit& rPrefabInit, Resources& rResources, ACtxBasic& rBasic) noexcept @@ -320,10 +332,10 @@ Session setup_prefabs( prefabs.task() = rBuilder.task().assign({tgSceneEvt, tgPrefabReq, tgPrefabEntReq, tgDrawMod, tgMeshMod, tgMatMod}).data( "Init Prefab drawables (single material)", - TopDataIds_t{ idPrefabInit, idResources, idDrawing, idDrawingRes, idMatEnts, idMatDirty, idActiveIds }, - wrap_args([] (ACtxPrefabInit& rPrefabInit, Resources& rResources, ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, EntSet_t& rMatEnts, EntVector_t& rMatDirty, ActiveReg_t const& rActiveIds) noexcept + TopDataIds_t{ idPrefabInit, idResources, idDrawing, idDrawingRes, idMatEnts, idMatDirty, idActiveIds }, + wrap_args([] (ACtxPrefabInit& rPrefabInit, Resources& rResources, ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, EntSet_t& rMatEnts, std::vector& rMatDirty, ActiveReg_t const& rActiveIds) noexcept { - rDrawing.m_drawable.ints().resize(rActiveIds.vec().capacity()); + rDrawing.resize_active(rActiveIds.capacity()); rMatEnts.ints().resize(rActiveIds.vec().capacity()); SysPrefabInit::init_drawing(rPrefabInit, rResources, rDrawing, rDrawingRes, {{rMatEnts, rMatDirty}}); })); @@ -353,89 +365,6 @@ Session setup_prefabs( return prefabs; } -Session setup_bounds( - Builder_t& rBuilder, - ArrayView const topData, - Tags& rTags, - Session const& scnCommon, - Session const& physics, - Session const& shapeSpawn) -{ - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_TAGS(physics, TESTAPP_PHYSICS); - OSP_SESSION_UNPACK_DATA(shapeSpawn, TESTAPP_SHAPE_SPAWN); - OSP_SESSION_UNPACK_TAGS(shapeSpawn, TESTAPP_SHAPE_SPAWN); - - Session bounds; - OSP_SESSION_ACQUIRE_DATA(bounds, topData, TESTAPP_BOUNDS); - OSP_SESSION_ACQUIRE_TAGS(bounds, rTags, TESTAPP_BOUNDS); - - top_emplace< EntSet_t > (topData, idBounds); - top_emplace< EntVector_t > (topData, idOutOfBounds); - - rBuilder.tag(tgBoundsSetMod) .depend_on({tgBoundsSetDel}); - rBuilder.tag(tgBoundsSetReq) .depend_on({tgBoundsSetDel, tgBoundsSetMod}); - rBuilder.tag(tgOutOfBoundsMod) .depend_on({tgOutOfBoundsPrv}); - - // Bounds are checked are after transforms are final (tgTransformReq) - // Out-of-bounds entities are added to rOutOfBounds, and are deleted - // at the start of next frame - - bounds.task() = rBuilder.task().assign({tgSceneEvt, tgTransformReq, tgBoundsSetReq, tgOutOfBoundsMod}).data( - "Check for out-of-bounds entities", - TopDataIds_t{ idBasic, idBounds, idOutOfBounds}, - wrap_args([] (ACtxBasic const& rBasic, EntSet_t const& rBounds, EntVector_t& rOutOfBounds) noexcept - { - rOutOfBounds.clear(); - for (std::size_t const ent : rBounds.ones()) - { - ACompTransform const &entTf = rBasic.m_transform.get(ActiveEnt(ent)); - if (entTf.m_transform.translation().z() < -10) - { - rOutOfBounds.push_back(ActiveEnt(ent)); - } - } - })); - - - bounds.task() = rBuilder.task().assign({tgSceneEvt, tgOutOfBoundsPrv, tgDelEntMod}).data( - "Delete out-of-bounds entities", - TopDataIds_t{ idBasic, idOutOfBounds, idDelEnts }, - wrap_args([] (ACtxBasic const& rBasic, EntVector_t const& rOutOfBounds, EntVector_t& rDelEnts) noexcept - { - rDelEnts.insert(rDelEnts.end(), rOutOfBounds.cbegin(), rOutOfBounds.cend()); - })); - - bounds.task() = rBuilder.task().assign({tgSceneEvt, tgSpawnReq, tgBoundsSetMod}).data( - "Add bounds to spawned shapes", - TopDataIds_t{ idSpawner, idSpawnerEnts, idBounds, idActiveIds }, - wrap_args([] (SpawnerVec_t const& rSpawner, EntVector_t const& rSpawnerEnts, EntSet_t& rBounds, ActiveReg_t const& rActiveIds) noexcept - { - rBounds .ints().resize(rActiveIds.vec().capacity()); - - for (std::size_t i = 0; i < rSpawner.size(); ++i) - { - SpawnShape const &spawn = rSpawner[i]; - if (spawn.m_mass == 0) - { - continue; - } - - ActiveEnt const root = rSpawnerEnts[i * 2]; - - rBounds.set(std::size_t(root)); - } - })); - - bounds.task() = rBuilder.task().assign({tgSceneEvt, tgDelTotalReq, tgBoundsSetDel}).data( - "Delete bounds components", - TopDataIds_t{idBounds, idDelTotal}, delete_ent_set); - - return bounds; -} - +#endif } // namespace testapp::scenes - - diff --git a/src/test_application/activescenes/scene_physics.h b/src/test_application/activescenes/scene_physics.h index b131e9f3..552830c7 100644 --- a/src/test_application/activescenes/scene_physics.h +++ b/src/test_application/activescenes/scene_physics.h @@ -45,7 +45,12 @@ struct SpawnShape osp::phys::EShape m_shape; }; -using SpawnerVec_t = std::vector; +struct ACtxShapeSpawner +{ + std::vector m_spawnRequest; + osp::active::ActiveEntVec_t m_ents; + osp::active::MaterialId m_materialId; +}; /** * @brief Physical properties for entities and generic Physics interface @@ -53,54 +58,40 @@ using SpawnerVec_t = std::vector; * Independent of whichever physics engine is used */ osp::Session setup_physics( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon); + osp::Session const& scene, + osp::Session const& commonScene); /** * @brief Newton Dynamics physics integration */ osp::Session setup_newton_physics( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& commonScene, osp::Session const& physics); /** * @brief Queues and logic for spawning physics shapes */ osp::Session setup_shape_spawn( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& scene, + osp::Session const& commonScene, osp::Session const& physics, - osp::Session const& material); + osp::active::MaterialId materialId); /** * @brief Queues and logic for spawning Prefab resources */ osp::Session setup_prefabs( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& commonScene, osp::Session const& physics, osp::Session const& material, osp::TopDataId idResources); -/** - * @brief Entity set to delete entities under Z = -10, added to spawned shapes - */ -osp::Session setup_bounds( - Builder_t& rBuilder, - osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, - osp::Session const& physics, - osp::Session const& shapeSpawn); - - } // namespace testapp::scenes diff --git a/src/test_application/activescenes/scene_renderer.cpp b/src/test_application/activescenes/scene_renderer.cpp index d7d2ed27..b2cf7af5 100644 --- a/src/test_application/activescenes/scene_renderer.cpp +++ b/src/test_application/activescenes/scene_renderer.cpp @@ -23,78 +23,166 @@ * SOFTWARE. */ #include "scene_renderer.h" +#include "scene_common.h" #include "scenarios.h" #include "identifiers.h" -#include "../ActiveApplication.h" +#include "CameraController.h" + +#include "../MagnumApplication.h" #include +#include + +#include #include #include #include #include #include #include +#include +#include + +#include #include +// for the 0xrrggbb_rgbf and angle literals +using namespace Magnum::Math::Literals; + using namespace osp; using namespace osp::shader; using namespace osp::active; +using namespace osp::universe; using osp::input::UserInputHandler; +using Magnum::GL::Mesh; + namespace testapp::scenes { -Session setup_magnum_application( - Builder_t& rBuilder, + + +Session setup_window_app( + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - TopDataId const idResources, - ActiveApplication::Arguments args) + Session const& application) { - Session magnum; - OSP_SESSION_ACQUIRE_DATA(magnum, topData, TESTAPP_APP_MAGNUM); - OSP_SESSION_ACQUIRE_TAGS(magnum, rTags, TESTAPP_APP_MAGNUM); + auto const tgApp = application .get_pipelines< PlApplication >(); + + Session out; + OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_WINDOW_APP); + auto const tgWin = out.create_pipelines(rBuilder); - // Order-dependent; ActiveApplication construction starts OpenGL context - auto &rUserInput = osp::top_emplace(topData, idUserInput, 12); - osp::top_emplace (topData, idActiveApp, args, rUserInput); - auto &rRenderGl = osp::top_emplace (topData, idRenderGl); + rBuilder.pipeline(tgWin.inputs).parent(tgApp.mainLoop).wait_for_signal(ModifyOrSignal); + auto &rUserInput = osp::top_emplace(topData, idUserInput, 12); config_controls(rUserInput); + + return out; +} + +Session setup_magnum( + TopTaskBuilder& rBuilder, + ArrayView const topData, + Session const& windowApp, + Session const& application, + MagnumApplication::Arguments args) +{ + OSP_DECLARE_GET_DATA_IDS(windowApp, TESTAPP_DATA_WINDOW_APP); + OSP_DECLARE_GET_DATA_IDS(application, TESTAPP_DATA_APPLICATION); + auto const tgApp = application .get_pipelines< PlApplication >(); + + auto& rUserInput = top_get(topData, idUserInput); + + Session out; + OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_MAGNUM); + auto const tgMgn = out.create_pipelines(rBuilder); + + out.m_cleanup = tgMgn.cleanup; + + rBuilder.pipeline(tgMgn.sync) .parent(tgApp.mainLoop).wait_for_signal(ModifyOrSignal); + rBuilder.pipeline(tgMgn.resync).parent(tgApp.mainLoop); + + rBuilder.pipeline(tgMgn.meshGL) .parent(tgMgn.sync); + rBuilder.pipeline(tgMgn.textureGL) .parent(tgMgn.sync); + rBuilder.pipeline(tgMgn.entMeshGL) .parent(tgMgn.sync); + rBuilder.pipeline(tgMgn.entTextureGL) .parent(tgMgn.sync); + + // Order-dependent; MagnumApplication construction starts OpenGL context, needed by RenderGL + /* unused */ top_emplace(topData, idActiveApp, args, rUserInput); + auto &rRenderGl = top_emplace (topData, idRenderGl); + SysRenderGL::setup_context(rRenderGl); - magnum.task() = rBuilder.task().assign({tgCleanupMagnumEvt, tgGlUse}).data( - "Clean up Magnum renderer", - TopDataIds_t{ idResources, idRenderGl}, - wrap_args([] (Resources& rResources, RenderGL& rRenderGl) noexcept + rBuilder.task() + .name ("Clean up Magnum renderer") + .run_on ({tgMgn.cleanup(Run_)}) + .push_to (out.m_tasks) + .args ({ idResources, idRenderGl}) + .func([] (Resources& rResources, RenderGL& rRenderGl) noexcept { SysRenderGL::clear_resource_owners(rRenderGl, rResources); rRenderGl = {}; // Needs the OpenGL thread for destruction - })); - magnum.m_tgCleanupEvt = tgCleanupMagnumEvt; + }); + + rBuilder.task() + .name ("Schedule GL Sync") + .schedules ({tgMgn.sync(Schedule)}) + .push_to (out.m_tasks) + .args ({ idMainLoopCtrl}) + .func([] (MainLoopControl const& rMainLoopCtrl) noexcept -> osp::TaskActions + { + return rMainLoopCtrl.doSync ? osp::TaskActions{} : osp::TaskAction::Cancel; + }); + + rBuilder.task() + .name ("Schedule GL Resync") + .schedules ({tgMgn.resync(Schedule)}) + .push_to (out.m_tasks) + .args ({ idMainLoopCtrl}) + .func([] (MainLoopControl const& rMainLoopCtrl) noexcept -> osp::TaskActions + { + return rMainLoopCtrl.doResync ? osp::TaskActions{} : osp::TaskAction::Cancel; + }); + - return magnum; + return out; } Session setup_scene_renderer( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, + Session const& application, + Session const& windowApp, Session const& magnum, - Session const& scnCommon, - TopDataId const idResources) + Session const& scene, + Session const& commonScene) { - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_TAGS(magnum, TESTAPP_APP_MAGNUM); - OSP_SESSION_UNPACK_DATA(magnum, TESTAPP_APP_MAGNUM); - - Session renderer; - OSP_SESSION_ACQUIRE_DATA(renderer, topData, TESTAPP_COMMON_RENDERER); - OSP_SESSION_ACQUIRE_TAGS(renderer, rTags, TESTAPP_COMMON_RENDERER); + OSP_DECLARE_GET_DATA_IDS(application, TESTAPP_DATA_APPLICATION); + OSP_DECLARE_GET_DATA_IDS(commonScene, TESTAPP_DATA_COMMON_SCENE); + OSP_DECLARE_GET_DATA_IDS(windowApp, TESTAPP_DATA_WINDOW_APP); + OSP_DECLARE_GET_DATA_IDS(magnum, TESTAPP_DATA_MAGNUM); + auto const tgApp = application .get_pipelines< PlApplication >(); + auto const tgCS = commonScene .get_pipelines< PlCommonScene >(); + auto const tgMgn = magnum .get_pipelines< PlMagnum >(); + + Session out; + OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_COMMON_RENDERER); + auto const tgSR = out.create_pipelines(rBuilder); + + rBuilder.pipeline(tgSR.render).parent(tgApp.mainLoop).wait_for_signal(ModifyOrSignal); + + rBuilder.pipeline(tgSR.fbo) .parent(tgSR.render); + rBuilder.pipeline(tgSR.scnRender) .parent(tgSR.render); + rBuilder.pipeline(tgSR.group) .parent(tgMgn.sync); + rBuilder.pipeline(tgSR.groupEnts) .parent(tgMgn.sync); + rBuilder.pipeline(tgSR.drawTransforms) .parent(tgMgn.sync); + rBuilder.pipeline(tgSR.camera) .parent(tgSR.render); + rBuilder.pipeline(tgSR.entMesh) .parent(tgMgn.sync); + rBuilder.pipeline(tgSR.entTexture) .parent(tgMgn.sync); top_emplace< ACtxSceneRenderGL > (topData, idScnRender); top_emplace< RenderGroup > (topData, idGroupFwd); @@ -105,164 +193,743 @@ Session setup_scene_renderer( rCamera.m_near = 1.0f; rCamera.m_fov = Magnum::Deg(45.0f); - rBuilder.tag(tgDrawGlMod) .depend_on({tgDrawGlDel}); - rBuilder.tag(tgDrawGlReq) .depend_on({tgDrawGlDel, tgDrawGlMod}); - rBuilder.tag(tgMeshGlReq) .depend_on({tgMeshGlMod}); - rBuilder.tag(tgTexGlReq) .depend_on({tgTexGlMod}); - rBuilder.tag(tgEntTexReq) .depend_on({tgEntTexMod}); - rBuilder.tag(tgEntMeshReq) .depend_on({tgEntMeshMod}); - rBuilder.tag(tgCameraReq) .depend_on({tgCameraMod}); - rBuilder.tag(tgGroupFwdMod) .depend_on({tgGroupFwdDel}); - rBuilder.tag(tgGroupFwdReq) .depend_on({tgGroupFwdDel, tgGroupFwdMod}); - rBuilder.tag(tgDrawTransformNew) .depend_on({tgDrawTransformDel}); - rBuilder.tag(tgDrawTransformMod) .depend_on({tgDrawTransformDel, tgDrawTransformNew}); - rBuilder.tag(tgDrawTransformReq) .depend_on({tgDrawTransformDel, tgDrawTransformNew, tgDrawTransformMod}); - - - renderer.task() = rBuilder.task().assign({tgSyncEvt, tgGlUse, tgTexGlMod, tgTexGlMod}).data( - "Synchronize used mesh and texture Resources with GL", - TopDataIds_t{ idDrawingRes, idResources, idRenderGl}, - wrap_args([] (ACtxDrawingRes const& rDrawingRes, osp::Resources& rResources, RenderGL& rRenderGl) noexcept + rBuilder.task() + .name ("Resize Scene Render containers to fit drawable entities") + .run_on ({tgCS.drawEntResized(Run)}) + .sync_with ({tgSR.entMesh(New), tgSR.scnRender(New)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idScnRender}) + .func([] (ACtxDrawing const& rDrawing, ACtxSceneRenderGL& rScnRender) noexcept { - SysRenderGL::sync_scene_resources(rDrawingRes, rResources, rRenderGl); - })); - - renderer.task() = rBuilder.task().assign({tgSyncEvt, tgTexGlReq, tgEntTexMod}).data( - "Assign GL textures to entities with scene textures", - TopDataIds_t{ idDrawing, idDrawingRes, idScnRender, idRenderGl}, - wrap_args([] (ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, ACtxSceneRenderGL& rScnRender, RenderGL& rRenderGl) noexcept + std::size_t const capacity = rDrawing.m_drawIds.capacity(); + rScnRender.m_drawTransform .resize(capacity); + rScnRender.m_diffuseTexId .resize(capacity); + rScnRender.m_meshId .resize(capacity); + }); + + rBuilder.task() + .name ("Compile Resource Meshes to GL") + .run_on ({tgCS.meshResDirty(UseOrRun)}) + .sync_with ({tgCS.mesh(Ready), tgMgn.meshGL(New), tgCS.entMeshDirty(UseOrRun)}) + .push_to (out.m_tasks) + .args ({ idDrawingRes, idResources, idRenderGl }) + .func([] (ACtxDrawingRes const& rDrawingRes, osp::Resources& rResources, RenderGL& rRenderGl) noexcept { - SysRenderGL::assign_textures(rDrawing.m_diffuseTex, rDrawingRes.m_texToRes, rDrawing.m_diffuseDirty, rScnRender.m_diffuseTexId, rRenderGl); - })); - - renderer.task() = rBuilder.task().assign({tgSyncEvt, tgTexGlReq, tgEntMeshMod, tgMeshReq}).data( - "Assign GL meshes to entities with scene meshes", - TopDataIds_t{ idDrawing, idDrawingRes, idScnRender, idRenderGl}, - wrap_args([] (ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, ACtxSceneRenderGL& rScnRender, RenderGL& rRenderGl) noexcept + SysRenderGL::compile_resource_meshes(rDrawingRes, rResources, rRenderGl); + }); + + rBuilder.task() + .name ("Compile Resource Textures to GL") + .run_on ({tgCS.textureResDirty(UseOrRun)}) + .sync_with ({tgCS.texture(Ready), tgMgn.textureGL(New)}) + .push_to (out.m_tasks) + .args ({ idDrawingRes, idResources, idRenderGl }) + .func([] (ACtxDrawingRes const& rDrawingRes, osp::Resources& rResources, RenderGL& rRenderGl) noexcept { - SysRenderGL::assign_meshes(rDrawing.m_mesh, rDrawingRes.m_meshToRes, rDrawing.m_meshDirty, rScnRender.m_meshId, rRenderGl); - })); - - // TODO: Separate forward renderer into different tasks to allow other - // rendering techniques - - renderer.task() = rBuilder.task().assign({tgRenderEvt, tgGlUse, tgDrawTransformReq, tgGroupFwdReq, tgDrawReq, tgCameraReq, tgEntTexMod, tgEntMeshMod}).data( - "Render and display scene", - TopDataIds_t{ idDrawing, idRenderGl, idGroupFwd, idCamera}, - wrap_args([] (ACtxDrawing const& rDrawing, RenderGL& rRenderGl, RenderGroup const& rGroupFwd, Camera const& rCamera) noexcept + SysRenderGL::compile_resource_textures(rDrawingRes, rResources, rRenderGl); + }); + + rBuilder.task() + .name ("Schedule Assign GL textures") + .schedules ({tgCS.entTextureDirty(Schedule_)}) + .sync_with ({tgCS.texture(Ready), tgCS.entTexture(Ready)}) + .push_to (out.m_tasks) + .args ({ idDrawing }) + .func([] (ACtxDrawing& rDrawing) noexcept -> TaskActions + { + return rDrawing.m_diffuseDirty.empty() ? TaskAction::Cancel : TaskActions{}; + }); + + rBuilder.task() + .name ("Sync GL textures to entities with scene textures") + .run_on ({tgCS.entTextureDirty(UseOrRun)}) + .sync_with ({tgCS.texture(Ready), tgCS.entTexture(Ready), tgMgn.textureGL(Ready), tgMgn.entTextureGL(Modify), tgCS.drawEntResized(Done)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idDrawingRes, idScnRender, idRenderGl }) + .func([] (ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, ACtxSceneRenderGL& rScnRender, RenderGL& rRenderGl) noexcept + { + SysRenderGL::sync_drawent_texture( + rDrawing.m_diffuseDirty.begin(), + rDrawing.m_diffuseDirty.end(), + rDrawing.m_diffuseTex, + rDrawingRes.m_texToRes, + rScnRender.m_diffuseTexId, + rRenderGl); + }); + + rBuilder.task() + .name ("Resync GL textures") + .run_on ({tgMgn.resync(Run)}) + .sync_with ({tgCS.texture(Ready), tgMgn.textureGL(Ready), tgMgn.entTextureGL(Modify), tgCS.drawEntResized(Done)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idDrawingRes, idScnRender, idRenderGl }) + .func([] (ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, ACtxSceneRenderGL& rScnRender, RenderGL& rRenderGl) noexcept + { + for (auto const drawEntInt : rDrawing.m_drawIds.bitview().zeros()) + { + SysRenderGL::sync_drawent_texture( + DrawEnt(drawEntInt), + rDrawing.m_diffuseTex, + rDrawingRes.m_texToRes, + rScnRender.m_diffuseTexId, + rRenderGl); + } + }); + + rBuilder.task() + .name ("Schedule Assign GL meshes") + .schedules ({tgCS.entMeshDirty(Schedule_)}) + .sync_with ({tgCS.mesh(Ready), tgCS.entMesh(Ready)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idDrawingRes, idScnRender, idRenderGl }) + .func([] (ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, ACtxSceneRenderGL& rScnRender, RenderGL& rRenderGl) noexcept -> TaskActions + { + return rDrawing.m_meshDirty.empty() ? TaskAction::Cancel : TaskActions{}; + }); + + rBuilder.task() + .name ("Sync GL meshes to entities with scene meshes") + .run_on ({tgCS.entMeshDirty(UseOrRun)}) + .sync_with ({tgCS.mesh(Ready), tgCS.entMesh(Ready), tgMgn.meshGL(Ready), tgMgn.entMeshGL(Modify), tgCS.drawEntResized(Done)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idDrawingRes, idScnRender, idRenderGl }) + .func([] (ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, ACtxSceneRenderGL& rScnRender, RenderGL& rRenderGl) noexcept + { + SysRenderGL::sync_drawent_mesh( + rDrawing.m_meshDirty.begin(), + rDrawing.m_meshDirty.end(), + rDrawing.m_mesh, + rDrawingRes.m_meshToRes, + rScnRender.m_meshId, + rRenderGl); + }); + + rBuilder.task() + .name ("Resync GL meshes") + .run_on ({tgMgn.resync(Run)}) + .sync_with ({tgCS.mesh(Ready), tgMgn.meshGL(Ready), tgMgn.entMeshGL(Modify), tgCS.drawEntResized(Done)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idDrawingRes, idScnRender, idRenderGl }) + .func([] (ACtxDrawing& rDrawing, ACtxDrawingRes& rDrawingRes, ACtxSceneRenderGL& rScnRender, RenderGL& rRenderGl) noexcept + { + for (auto const drawEntInt : rDrawing.m_drawIds.bitview().zeros()) + { + SysRenderGL::sync_drawent_mesh( + DrawEnt(drawEntInt), + rDrawing.m_mesh, + rDrawingRes.m_meshToRes, + rScnRender.m_meshId, + rRenderGl); + } + }); + + rBuilder.task() + .name ("Bind and display off-screen FBO") + .run_on ({tgSR.render(Run)}) + .sync_with ({tgSR.fbo(EStgFBO::Bind)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idRenderGl, idGroupFwd, idCamera }) + .func([] (ACtxDrawing const& rDrawing, RenderGL& rRenderGl, RenderGroup const& rGroupFwd, Camera const& rCamera) noexcept { using Magnum::GL::Framebuffer; using Magnum::GL::FramebufferClear; - using Magnum::GL::Texture2D; - // Bind offscreen FBO Framebuffer &rFbo = rRenderGl.m_fbo; rFbo.bind(); + Magnum::GL::Texture2D &rFboColor = rRenderGl.m_texGl.get(rRenderGl.m_fboColor); + SysRenderGL::display_texture(rRenderGl, rFboColor); + // Clear it - rFbo.clear( FramebufferClear::Color | FramebufferClear::Depth + rFbo.clear( FramebufferClear::Color | FramebufferClear::Depth | FramebufferClear::Stencil); - - + }); + + rBuilder.task() + .name ("Calculate draw transforms") + .run_on ({tgSR.render(Run)}) + .sync_with ({tgCS.hierarchy(Ready), tgCS.transform(Ready), tgCS.activeEnt(Ready), tgSR.drawTransforms(Modify_), tgCS.drawEnt(Ready), tgCS.drawEntResized(Done)}) + .push_to (out.m_tasks) + .args ({ idBasic, idDrawing, idScnRender }) + .func([] (ACtxBasic const& rBasic, ACtxDrawing const& rDrawing, ACtxSceneRenderGL& rScnRender) noexcept + { + auto rootChildren = SysSceneGraph::children(rBasic.m_scnGraph); + SysRender::update_draw_transforms( + rBasic .m_scnGraph, + rDrawing .m_activeToDraw, + rBasic .m_transform, + rScnRender .m_drawTransform, + rDrawing .m_needDrawTf, + rootChildren.begin(), + rootChildren.end()); + }); + + rBuilder.task() + .name ("Render Entities") + .run_on ({tgSR.render(Run)}) + .sync_with ({tgSR.group(Ready), tgSR.groupEnts(Ready), tgSR.camera(Ready), tgSR.drawTransforms(UseOrRun), tgSR.entMesh(Ready), tgSR.entTexture(Ready), + tgMgn.entMeshGL(Ready), tgMgn.entTextureGL(Ready), + tgCS.drawEnt(Ready)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idRenderGl, idGroupFwd, idCamera }) + .func([] (ACtxDrawing const& rDrawing, RenderGL& rRenderGl, RenderGroup const& rGroupFwd, Camera const& rCamera, WorkerContext ctx) noexcept + { ViewProjMatrix viewProj{rCamera.m_transform.inverted(), rCamera.perspective()}; // Forward Render fwd_opaque group to FBO SysRenderGL::render_opaque(rGroupFwd, rDrawing.m_visible, viewProj); + }); + + rBuilder.task() + .name ("Delete entities from render groups") + .run_on ({tgCS.drawEntDelete(UseOrRun)}) + .sync_with ({tgSR.groupEnts (Delete)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idGroupFwd, idDrawEntDel }) + .func([] (ACtxDrawing const& rDrawing, RenderGroup& rGroup, DrawEntVec_t const& rDrawEntDel) noexcept + { + for (DrawEnt const drawEnt : rDrawEntDel) + { + rGroup.m_entities.remove(drawEnt); + } + }); - Texture2D &rFboColor = rRenderGl.m_texGl.get(rRenderGl.m_fboColor); - SysRenderGL::display_texture(rRenderGl, rFboColor); - })); + return out; +} +Session setup_shader_visualizer( + TopTaskBuilder& rBuilder, + ArrayView const topData, + Session const& magnum, + Session const& scene, + Session const& commonScene, + Session const& scnRender, + MaterialId const materialId) +{ + OSP_DECLARE_GET_DATA_IDS(commonScene, TESTAPP_DATA_COMMON_SCENE); + OSP_DECLARE_GET_DATA_IDS(scnRender, TESTAPP_DATA_COMMON_RENDERER); + OSP_DECLARE_GET_DATA_IDS(magnum, TESTAPP_DATA_MAGNUM); + auto const tgScn = scene .get_pipelines(); + auto const tgCS = commonScene .get_pipelines(); + auto const tgSR = scnRender .get_pipelines(); + auto const tgMgn = magnum .get_pipelines(); + + auto &rScnRender = top_get< ACtxSceneRenderGL > (topData, idScnRender); + auto &rRenderGl = top_get< RenderGL > (topData, idRenderGl); + + Session out; + OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_SHADER_VISUALIZER) + auto &rDrawVisual = top_emplace< ACtxDrawMeshVisualizer >(topData, idDrawShVisual); + rDrawVisual.m_materialId = materialId; - renderer.task() = rBuilder.task().assign({tgSyncEvt, tgDelTotalReq, tgDrawGlDel}).data( - "Delete GL components", - TopDataIds_t{ idScnRender, idDelTotal}, - wrap_args([] (ACtxSceneRenderGL& rScnRender, EntVector_t const& rDelTotal) noexcept + rDrawVisual.m_shader = MeshVisualizer{ MeshVisualizer::Configuration{}.setFlags(MeshVisualizer::Flag::Wireframe) }; + rDrawVisual.assign_pointers(rScnRender, rRenderGl); + + // Default colors + rDrawVisual.m_shader.setWireframeColor({0.7f, 0.5f, 0.7f, 1.0f}); + rDrawVisual.m_shader.setColor({0.2f, 0.1f, 0.5f, 1.0f}); + + if (materialId == lgrn::id_null()) { - SysRenderGL::update_delete(rScnRender, std::cbegin(rDelTotal), std::cend(rDelTotal)); - })); + return out; + } + + rBuilder.task() + .name ("Sync MeshVisualizer shader entities") + .run_on ({tgMgn.sync(Run)}) + .sync_with ({tgSR.groupEnts(Modify), tgSR.group(Modify), tgCS.materialDirty(UseOrRun)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idGroupFwd, idDrawShVisual}) + .func([] (ACtxDrawing const& rDrawing, RenderGroup& rGroupFwd, ACtxDrawMeshVisualizer& rDrawShVisual) noexcept + { + Material const &rMat = rDrawing.m_materials[rDrawShVisual.m_materialId]; + sync_drawent_visualizer(rMat.m_dirty.begin(), rMat.m_dirty.end(), rMat.m_ents, rGroupFwd.m_entities, rDrawShVisual); + }); + + rBuilder.task() + .name ("Resync MeshVisualizer") + .run_on ({tgMgn.resync(Run)}) + .sync_with ({tgSR.groupEnts(Modify), tgSR.group(Modify)}) + .push_to (out.m_tasks) + .args ({ idDrawing, idGroupFwd, idDrawShVisual}) + .func([] (ACtxDrawing const& rDrawing, RenderGroup& rGroupFwd, ACtxDrawMeshVisualizer& rDrawShVisual) noexcept + { + Material const &rMat = rDrawing.m_materials[rDrawShVisual.m_materialId]; + for (auto const drawEntInt : rMat.m_ents.ones()) + { + sync_drawent_visualizer(DrawEnt(drawEntInt), rMat.m_ents, rGroupFwd.m_entities, rDrawShVisual); + } + }); + + return out; +} - renderer.task() = rBuilder.task().assign({tgSyncEvt, tgHierReq, tgTransformReq, tgDrawTransformMod}).data( - "Calculate draw transforms", - TopDataIds_t{ idBasic, idDrawing, idScnRender}, - wrap_args([] (ACtxBasic const& rBasic, ACtxDrawing const& rDrawing, ACtxSceneRenderGL& rScnRender) noexcept +#if 0 +Session setup_shader_flat( + TopTaskBuilder& rBuilder, + ArrayView const topData, + Session const& magnum, + Session const& commonScene, + Session const& scnRender, + Session const& material) +{ + using osp::shader::Flat; + + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(commonScene, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(magnum, TESTAPP_APP_MAGNUM); + OSP_SESSION_UNPACK_TAGS(scnRender, TESTAPP_COMMON_RENDERER); + OSP_SESSION_UNPACK_DATA(scnRender, TESTAPP_COMMON_RENDERER); + auto &rScnRender = top_get< ACtxSceneRenderGL > (topData, idScnRender); + auto &rRenderGl = top_get< RenderGL > (topData, idRenderGl); + + Session shFlat; + OSP_SESSION_ACQUIRE_DATA(shFlat, topData, TESTAPP_SHADER_FLAT) + auto &rDrawFlat = top_emplace< ACtxDrawFlat >(topData, idDrawShFlat); + + rDrawFlat.m_shaderDiffuse = Flat{Flat::Configuration{}.setFlags(Flat::Flag::Textured)}; + rDrawFlat.m_shaderUntextured = Flat{Flat::Configuration{}}; + rDrawFlat.assign_pointers(rScnRender, rRenderGl); + + if (material.m_dataIds.empty()) { - auto rootChildren = SysSceneGraph::children(rBasic.m_scnGraph); - SysRender::update_draw_transforms(rBasic.m_scnGraph, rBasic.m_transform, rScnRender.m_drawTransform, rDrawing.m_drawable, std::begin(rootChildren), std::end(rootChildren)); - })); + return shFlat; + } + OSP_SESSION_UNPACK_TAGS(material, TESTAPP_MATERIAL); + OSP_SESSION_UNPACK_DATA(material, TESTAPP_MATERIAL); - renderer.task() = rBuilder.task().assign({tgSyncEvt, tgDelTotalReq, tgGroupFwdDel}).data( - "Delete entities from render groups", - TopDataIds_t{ idGroupFwd, idDelTotal}, - wrap_args([] (RenderGroup& rGroup, EntVector_t const& rDelTotal) noexcept + shFlat.task() = rBuilder.task().assign({tgSyncEvt, tgMatReq, tgDrawReq, PlexGlReq, tgGroupFwdMod}).data( + "Sync Flat shader entities", + TopDataIds_t{ idMatDirty, idMatEnts, idDrawing, idScnRender, idGroupFwd, idDrawShFlat}, + wrap_args([] (std::vector const& rMatDirty, EntSet_t const& rMatEnts, ACtxDrawing const& rDrawing, ACtxSceneRenderGL const& rScnRender, RenderGroup& rGroupFwd, ACtxDrawFlat& rDrawShFlat) noexcept { - rGroup.m_entities.remove(std::cbegin(rDelTotal), std::cend(rDelTotal)); + sync_flat(std::cbegin(rMatDirty), std::cend(rMatDirty), rMatEnts, &rGroupFwd.m_entities, nullptr, rDrawing.m_drawBasic, rScnRender.m_diffuseTexId, rDrawShFlat); })); - return renderer; + return shFlat; } -Session setup_shader_visualizer( - Builder_t& rBuilder, + +Session setup_shader_phong( + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, Session const& magnum, - Session const& scnCommon, + Session const& commonScene, Session const& scnRender, Session const& material) { - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); + using osp::shader::Phong; + + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(commonScene, TESTAPP_COMMON_SCENE); OSP_SESSION_UNPACK_DATA(magnum, TESTAPP_APP_MAGNUM); OSP_SESSION_UNPACK_TAGS(scnRender, TESTAPP_COMMON_RENDERER); OSP_SESSION_UNPACK_DATA(scnRender, TESTAPP_COMMON_RENDERER); - OSP_SESSION_UNPACK_TAGS(material, TESTAPP_MATERIAL); - OSP_SESSION_UNPACK_DATA(material, TESTAPP_MATERIAL); auto &rScnRender = top_get< ACtxSceneRenderGL > (topData, idScnRender); auto &rRenderGl = top_get< RenderGL > (topData, idRenderGl); - Session visualizer; - OSP_SESSION_ACQUIRE_DATA(visualizer, topData, TESTAPP_SHADER_VISUALIZER) - auto &rDrawVisual = top_emplace< ACtxDrawMeshVisualizer >(topData, idDrawVisual); + Session shPhong; + OSP_SESSION_ACQUIRE_DATA(shPhong, topData, TESTAPP_SHADER_PHONG) + auto &rDrawPhong = top_emplace< ACtxDrawPhong >(topData, idDrawShPhong); - rDrawVisual.m_shader = MeshVisualizer{ MeshVisualizer::Configuration{}.setFlags(MeshVisualizer::Flag::Wireframe) }; - rDrawVisual.assign_pointers(rScnRender, rRenderGl); - visualizer.task() = rBuilder.task().assign({tgSyncEvt, tgMatReq, tgGroupFwdMod}).data( - "Sync MeshVisualizer shader entities", - TopDataIds_t{ idMatDirty, idMatEnts, idGroupFwd, idDrawVisual}, - wrap_args([] (EntVector_t const& rMatDirty, EntSet_t const& rMatEnts, RenderGroup& rGroup, ACtxDrawMeshVisualizer& rVisualizer) noexcept + auto const texturedFlags = Phong::Flag::DiffuseTexture | Phong::Flag::AlphaMask | Phong::Flag::AmbientTexture; + rDrawPhong.m_shaderDiffuse = Phong{Phong::Configuration{}.setFlags(texturedFlags).setLightCount(2)}; + rDrawPhong.m_shaderUntextured = Phong{Phong::Configuration{}.setLightCount(2)}; + rDrawPhong.assign_pointers(rScnRender, rRenderGl); + + if (material.m_dataIds.empty()) { - sync_visualizer(std::cbegin(rMatDirty), std::cend(rMatDirty), rMatEnts, rGroup.m_entities, rVisualizer); + return shPhong; + } + OSP_SESSION_UNPACK_TAGS(material, TESTAPP_MATERIAL); + OSP_SESSION_UNPACK_DATA(material, TESTAPP_MATERIAL); + + shPhong.task() = rBuilder.task().assign({tgSyncEvt, tgMatReq, tgDrawReq, PlexGlReq, tgGroupFwdMod}).data( + "Sync Phong shader entities", + TopDataIds_t{ idMatDirty, idMatEnts, idDrawing, idScnRender, idGroupFwd, idDrawShPhong}, + wrap_args([] (std::vector const& rMatDirty, EntSet_t const& rMatEnts, ACtxDrawing const& rDrawing, ACtxSceneRenderGL const& rScnRender, RenderGroup& rGroupFwd, ACtxDrawPhong& rDrawShPhong) noexcept + { + sync_phong(std::cbegin(rMatDirty), std::cend(rMatDirty), rMatEnts, &rGroupFwd.m_entities, nullptr, rDrawing.m_drawBasic, rScnRender.m_diffuseTexId, rDrawShPhong); })); - visualizer.task() = rBuilder.task().assign({tgSyncEvt, tgMatReq, tgHierReq, tgTransformReq, tgDrawTransformNew}).data( - "Add draw transforms to mesh visualizer", - TopDataIds_t{ idMatDirty, idScnRender}, - wrap_args([] (EntVector_t const& rMatDirty, ACtxSceneRenderGL& rScnRender) noexcept + return shPhong; +} + +struct IndicatorMesh +{ + Magnum::Color4 m_color; + MeshIdOwner_t m_mesh; +}; + + +Session setup_thrust_indicators( + TopTaskBuilder& rBuilder, + ArrayView const topData, + Session const& magnum, + Session const& commonScene, + Session const& parts, + Session const& signalsFloat, + Session const& scnRender, + Session const& cameraCtrl, + Session const& shFlat, + TopDataId const idResources, + PkgId const pkg) +{ + + + using namespace osp::link; + using adera::gc_mtMagicRocket; + using adera::ports_magicrocket::gc_throttleIn; + using adera::ports_magicrocket::gc_multiplierIn; + + static constexpr float indicatorScale = 0.0001f; + + OSP_SESSION_UNPACK_TAGS(magnum, TESTAPP_APP_MAGNUM); + OSP_SESSION_UNPACK_DATA(magnum, TESTAPP_APP_MAGNUM); + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(commonScene, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_TAGS(parts, TESTAPP_PARTS); + OSP_SESSION_UNPACK_DATA(parts, TESTAPP_PARTS); + OSP_SESSION_UNPACK_DATA(signalsFloat, TESTAPP_SIGNALS_FLOAT) + OSP_SESSION_UNPACK_TAGS(signalsFloat, TESTAPP_SIGNALS_FLOAT); + OSP_SESSION_UNPACK_TAGS(scnRender, TESTAPP_COMMON_RENDERER); + OSP_SESSION_UNPACK_DATA(scnRender, TESTAPP_COMMON_RENDERER); + + OSP_SESSION_UNPACK_DATA(cameraCtrl, TESTAPP_CAMERA_CTRL); + OSP_SESSION_UNPACK_TAGS(cameraCtrl, TESTAPP_CAMERA_CTRL); + OSP_SESSION_UNPACK_DATA(shFlat, TESTAPP_SHADER_FLAT); + + auto &rResources = top_get< Resources > (topData, idResources); + auto &rBasic = top_get< ACtxBasic > (topData, idBasic); + auto &rActiveIds = top_get< ActiveReg_t > (topData, idActiveIds); + auto &rDrawing = top_get< ACtxDrawing > (topData, idDrawing); + auto &rDrawingRes = top_get< ACtxDrawingRes > (topData, idDrawingRes); + + Session thrustIndicator; +#if 0 // RENDERENT + OSP_SESSION_ACQUIRE_DATA(thrustIndicator, topData, TESTAPP_INDICATOR); + thrustIndicator.m_tgCleanupEvt = tgCleanupMagnumEvt; + + auto &rIndicator = top_emplace(topData, idIndicator); + rIndicator.m_color = { 1.0f, 0.0f, 0.0f, 1.0f }; + rIndicator.m_mesh = SysRender::add_drawable_mesh(rDrawing, rDrawingRes, rResources, pkg, "cone"); + + thrustIndicator.task() = rBuilder.task().assign({tgRenderEvt, tgGlUse, tgDrawTransformReq, tgBindFboReq, tgFwdRenderMod, tgCamCtrlReq}).data( + "Render cursor", + TopDataIds_t{ idRenderGl, idCamera, idDrawingRes, idScnRender, idScnParts, idSigValFloat, idDrawShFlat, idCamCtrl, idIndicator}, + wrap_args([] (RenderGL& rRenderGl, Camera const& rCamera, ACtxDrawingRes const& rDrawingRes, ACtxSceneRenderGL const& rScnRender, ACtxParts const& rScnParts, SignalValues_t const& rSigValFloat, ACtxDrawFlat& rDrawShFlat, ACtxCameraController const& rCamCtrl, IndicatorMesh& rIndicator) noexcept { - SysRender::assure_draw_transforms(rScnRender.m_drawTransform, std::cbegin(rMatDirty), std::cend(rMatDirty)); + + ResId const indicatorResId = rDrawingRes.m_meshToRes.at(rIndicator.m_mesh.value()); + MeshGlId const indicatorMeshGlId = rRenderGl.m_resToMesh.at(indicatorResId); + Mesh& rIndicatorMeshGl = rRenderGl.m_meshGl.get(indicatorMeshGlId); + + ViewProjMatrix viewProj{rCamera.m_transform.inverted(), rCamera.perspective()}; + + //auto const matrix = viewProj.m_viewProj * Matrix4::translation(rCamCtrl.m_target.value()); + + PerMachType const& rockets = rScnParts.m_machines.m_perType[gc_mtMagicRocket]; + Nodes const& floats = rScnParts.m_nodePerType[gc_ntSigFloat]; + + for (MachLocalId const localId : rockets.m_localIds.bitview().zeros()) + { + MachAnyId const anyId = rockets.m_localToAny[localId]; + PartId const part = rScnParts.m_machineToPart[anyId]; + ActiveEnt const partEnt = rScnParts.m_partToActive[part]; + + auto const& portSpan = floats.m_machToNode[anyId]; + NodeId const throttleIn = connected_node(portSpan, gc_throttleIn.m_port); + NodeId const multiplierIn = connected_node(portSpan, gc_multiplierIn.m_port); + + float const throttle = std::clamp(rSigValFloat[throttleIn], 0.0f, 1.0f); + float const multiplier = rSigValFloat[multiplierIn]; + float const thrustMag = throttle * multiplier; + + if (thrustMag == 0.0f) + { + continue; + } + + Matrix4 const& rocketDrawTf = rScnRender.m_drawTransform.get(partEnt); + + auto const& matrix = Matrix4::from(rocketDrawTf.rotationNormalized(), rocketDrawTf.translation()) + * Matrix4::scaling({1.0f, 1.0f, thrustMag * indicatorScale}) + * Matrix4::translation({0.0f, 0.0f, -1.0f}) + * Matrix4::scaling({0.2f, 0.2f, 1.0f}); + + rDrawShFlat.m_shaderUntextured + .setColor(rIndicator.m_color) + .setTransformationProjectionMatrix(viewProj.m_viewProj * matrix) + .draw(rIndicatorMeshGl); + } })); + thrustIndicator.task() = rBuilder.task().assign({tgCleanupMagnumEvt}).data( + "Clean up thrust indicator resource owners", + TopDataIds_t{ idDrawing, idIndicator}, + wrap_args([] (ACtxDrawing& rDrawing, IndicatorMesh& rIndicator) noexcept + { + rDrawing.m_meshRefCounts.ref_release(std::move(rIndicator.m_mesh)); + })); - return visualizer; -} + // Draw transforms in part entities are required for drawing indicators. + // This solution of adding them here is a bit janky but it works. + thrustIndicator.task() = rBuilder.task().assign({tgSyncEvt, tgPartReq}).data( + "Add draw transforms to rocket entities", + TopDataIds_t{ idScnParts, idScnRender}, + wrap_args([] (ACtxParts const& rScnParts, ACtxSceneRenderGL& rScnRender) noexcept + { + for (PartId const part : rScnParts.m_partDirty) + { + // TODO: maybe first check if the part contains any rocket machines + + ActiveEnt const ent = rScnParts.m_partToActive[part]; + if ( ! rScnRender.m_drawTransform.contains(ent) ) + { + rScnRender.m_drawTransform.emplace(ent); + } + } + })); + + // Called when scene is reopened + thrustIndicator.task() = rBuilder.task().assign({tgResyncEvt, tgPartReq}).data( + "Add draw transforms to all rocket entities", + TopDataIds_t{ idScnParts, idScnRender}, + wrap_args([] (ACtxParts const& rScnParts, ACtxSceneRenderGL& rScnRender) noexcept + { + for (PartId const part : rScnParts.m_partIds.bitview().zeros()) + { + ActiveEnt const ent = rScnParts.m_partToActive[part]; + if ( ! rScnRender.m_drawTransform.contains(ent) ) + { + rScnRender.m_drawTransform.emplace(ent); + } + } + })); +#endif + return thrustIndicator; +} -// TODO -#if 0 -Session setup_shader_phong() +Session setup_cursor( + TopTaskBuilder& rBuilder, + ArrayView const topData, + Session const& magnum, + Session const& commonScene, + Session const& scnRender, + Session const& cameraCtrl, + Session const& shFlat, + TopDataId const idResources, + PkgId const pkg) { - // Setup Phong shaders - auto const texturedFlags = Phong::Flag::DiffuseTexture | Phong::Flag::AlphaMask | Phong::Flag::AmbientTexture; - rDrawPhong.m_shaderDiffuse = Phong{texturedFlags, 2}; - rDrawPhong.m_shaderUntextured = Phong{{}, 2}; - rDrawPhong.assign_pointers(rScnRender, rRenderGl); + OSP_SESSION_UNPACK_TAGS(magnum, TESTAPP_APP_MAGNUM); + OSP_SESSION_UNPACK_DATA(magnum, TESTAPP_APP_MAGNUM); + OSP_SESSION_UNPACK_TAGS(scnRender, TESTAPP_COMMON_RENDERER); + OSP_SESSION_UNPACK_DATA(scnRender, TESTAPP_COMMON_RENDERER); + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(commonScene, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(cameraCtrl, TESTAPP_CAMERA_CTRL); + OSP_SESSION_UNPACK_TAGS(cameraCtrl, TESTAPP_CAMERA_CTRL); + OSP_SESSION_UNPACK_DATA(shFlat, TESTAPP_SHADER_FLAT); + + auto &rResources = top_get< Resources > (topData, idResources); + auto &rBasic = top_get< ACtxBasic > (topData, idBasic); + auto &rActiveIds = top_get< ActiveReg_t > (topData, idActiveIds); + auto &rDrawing = top_get< ACtxDrawing > (topData, idDrawing); + auto &rDrawingRes = top_get< ACtxDrawingRes > (topData, idDrawingRes); + + Session cursor; + OSP_SESSION_ACQUIRE_DATA(cursor, topData, TESTAPP_INDICATOR); + cursor.m_tgCleanupEvt = tgCleanupMagnumEvt; + + auto &rCursorData = top_emplace(topData, idIndicator); + rCursorData.m_color = { 0.0f, 1.0f, 0.0f, 1.0f }; + rCursorData.m_mesh = SysRender::add_drawable_mesh(rDrawing, rDrawingRes, rResources, pkg, "cubewire"); + + cursor.task() = rBuilder.task().assign({tgRenderEvt, tgGlUse, tgBindFboReq, tgFwdRenderMod, tgCamCtrlReq}).data( + "Render cursor", + TopDataIds_t{ idRenderGl, idCamera, idDrawingRes, idDrawShFlat, idCamCtrl, idIndicator }, + wrap_args([] (RenderGL& rRenderGl, Camera const& rCamera, ACtxDrawingRes const& rDrawingRes, ACtxDrawFlat& rDrawShFlat, ACtxCameraController const& rCamCtrl, IndicatorMesh& rIndicator) noexcept + { + ResId const cursorResId = rDrawingRes.m_meshToRes.at(rIndicator.m_mesh.value()); + MeshGlId const cursorMeshGlId = rRenderGl.m_resToMesh.at(cursorResId); + Mesh& rCursorMeshGl = rRenderGl.m_meshGl.get(cursorMeshGlId); + + ViewProjMatrix viewProj{rCamera.m_transform.inverted(), rCamera.perspective()}; + + auto const matrix = viewProj.m_viewProj * Matrix4::translation(rCamCtrl.m_target.value()); + + rDrawShFlat.m_shaderUntextured + .setColor(rIndicator.m_color) + .setTransformationProjectionMatrix(matrix) + .draw(rCursorMeshGl); + })); + cursor.task() = rBuilder.task().assign({tgCleanupMagnumEvt}).data( + "Clean up cursor resource owners", + TopDataIds_t{ idDrawing, idIndicator}, + wrap_args([] (ACtxDrawing& rDrawing, IndicatorMesh& rIndicator) noexcept + { + rDrawing.m_meshRefCounts.ref_release(std::move(rIndicator.m_mesh)); + })); + + return cursor; } -#endif +Session setup_uni_test_planets_renderer( + TopTaskBuilder& rBuilder, + ArrayView const topData, + Session const& magnum, + Session const& scnRender, + Session const& commonScene, + Session const& cameraCtrl, + Session const& visualizer, + Session const& uniCore, + Session const& uniScnFrame, + Session const& uniTestPlanets) +{ + Session uniTestPlanetsRdr; + + OSP_SESSION_UNPACK_TAGS(magnum, TESTAPP_APP_MAGNUM); + OSP_SESSION_UNPACK_DATA(magnum, TESTAPP_APP_MAGNUM); + OSP_SESSION_UNPACK_TAGS(scnRender, TESTAPP_COMMON_RENDERER); + OSP_SESSION_UNPACK_DATA(scnRender, TESTAPP_COMMON_RENDERER); + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(commonScene, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(cameraCtrl, TESTAPP_CAMERA_CTRL); + OSP_SESSION_UNPACK_TAGS(cameraCtrl, TESTAPP_CAMERA_CTRL); + OSP_SESSION_UNPACK_DATA(visualizer, TESTAPP_SHADER_VISUALIZER); + OSP_SESSION_UNPACK_TAGS(uniCore, TESTAPP_UNI_CORE); + OSP_SESSION_UNPACK_DATA(uniCore, TESTAPP_UNI_CORE); + OSP_SESSION_UNPACK_TAGS(uniScnFrame, TESTAPP_UNI_SCENEFRAME); + OSP_SESSION_UNPACK_DATA(uniScnFrame, TESTAPP_UNI_SCENEFRAME); + OSP_SESSION_UNPACK_DATA(uniTestPlanets, TESTAPP_UNI_PLANETS); + + //OSP_SESSION_ACQUIRE_DATA(uniTestPlanets, topData, TESTAPP_UNI_PLANETS); + + uniTestPlanetsRdr.task() = rBuilder.task().assign({tgRenderEvt, tgScnFramePosMod, tgCamCtrlMod}).data( + "Position SceneFrame center Camera Controller", + TopDataIds_t{ idCamCtrl, idScnFrame}, + wrap_args([] (ACtxCameraController& rCamCtrl, SceneFrame& rScnFrame) noexcept + { + if ( ! rCamCtrl.m_target.has_value()) + { + return; + } + Vector3 &rCamPl = rCamCtrl.m_target.value(); + + // check origin translation + // ADL used for Magnum::Math::sign/floor/abs + float const maxDist = 512.0f; + Vector3 const translate = sign(rCamPl) * floor(abs(rCamPl) / maxDist) * maxDist; + + if ( ! translate.isZero()) + { + rCamCtrl.m_transform.translation() -= translate; + rCamPl -= translate; + + // a bit janky to modify universe stuff directly here, but it works lol + Vector3 const rotated = Quaternion(rScnFrame.m_rotation).transformVector(translate); + rScnFrame.m_position += Vector3g(math::mul_2pow(rotated, rScnFrame.m_precision)); + } + + rScnFrame.m_scenePosition = Vector3g(math::mul_2pow(rCamCtrl.m_target.value(), rScnFrame.m_precision)); + + })); + + uniTestPlanetsRdr.task() = rBuilder.task().assign({tgRenderEvt, tgGlUse, tgBindFboReq, tgFwdRenderMod, tgDrawReq, tgCameraReq, tgScnFramePosReq}).data( + "Render test planets", + TopDataIds_t{ idRenderGl, idCamera, idDrawShVisual, idDrawingRes, idNMesh, idUniverse, idScnFrame, idPlanetMainSpace}, + wrap_args([] (RenderGL& rRenderGl, Camera const& rCamera, ACtxDrawMeshVisualizer& rDrawShVisual, ACtxDrawingRes const& rDrawingRes, NamedMeshes& rNMesh, Universe& rUniverse, SceneFrame const& rScnFrame, CoSpaceId const planetMainSpace) noexcept + { + CoSpaceCommon &rMainSpace = rUniverse.m_coordCommon[planetMainSpace]; + auto const [x, y, z] = sat_views(rMainSpace.m_satPositions, rMainSpace.m_data, rMainSpace.m_satCount); + auto const [qx, qy, qz, qw] = sat_views(rMainSpace.m_satRotations, rMainSpace.m_data, rMainSpace.m_satCount); + + // Calculate transform from universe to area/local-space for rendering. + // This can be generalized by finding a common ancestor within the tree + // of coordinate spaces. Since there's only two possibilities, an if + // statement works. + CoordTransformer mainToArea; + if (rScnFrame.m_parent == planetMainSpace) + { + mainToArea = coord_parent_to_child(rMainSpace, rScnFrame); + } + else + { + CoSpaceId const landedId = rScnFrame.m_parent; + CoSpaceCommon &rLanded = rUniverse.m_coordCommon[landedId]; + + CoSpaceTransform const landedTf = coord_get_transform(rLanded, rLanded, x, y, z, qx, qy, qz, qw); + CoordTransformer const mainToLanded = coord_parent_to_child(rMainSpace, landedTf); + CoordTransformer const landedToArea = coord_parent_to_child(landedTf, rScnFrame); + + mainToArea = coord_composite(landedToArea, mainToLanded); + } + Quaternion const mainToAreaRot{mainToArea.rotation()}; + + float const scale = math::mul_2pow(1.0f, -rMainSpace.m_precision); + + ViewProjMatrix viewProj{rCamera.m_transform.inverted(), rCamera.perspective()}; + + MeshId const sphereMeshId = rNMesh.m_shapeToMesh[phys::EShape::Sphere]; + ResId const sphereResId = rDrawingRes.m_meshToRes.at(sphereMeshId); + MeshGlId const sphereMeshGlId = rRenderGl.m_resToMesh.at(sphereResId); + Mesh& rSphereMeshGl = rRenderGl.m_meshGl.get(sphereMeshGlId); + + using Magnum::GL::Renderer; + Renderer::enable(Renderer::Feature::DepthTest); + Renderer::enable(Renderer::Feature::FaceCulling); + Renderer::disable(Renderer::Feature::Blending); + Renderer::setDepthMask(true); + + // Draw center indicator + Vector3 const centerPos = Vector3(mainToArea.transform_position({})) * scale; + rDrawShVisual.m_shader + .setTransformationMatrix( + viewProj.m_view + * Matrix4::translation(centerPos) + * Matrix4{mainToAreaRot.toMatrix()} + * Matrix4::scaling({500, 50, 50})) + .draw(rSphereMeshGl) + .setTransformationMatrix( + viewProj.m_view + * Matrix4::translation(centerPos) + * Matrix4{mainToAreaRot.toMatrix()} + * Matrix4::scaling({50, 500, 50})) + .draw(rSphereMeshGl) + .setTransformationMatrix( + viewProj.m_view + * Matrix4::translation(centerPos) + * Matrix4{mainToAreaRot.toMatrix()} + * Matrix4::scaling({50, 50, 500}) ) + .draw(rSphereMeshGl); + + // Draw planets + for (std::size_t i = 0; i < rMainSpace.m_satCount; ++i) + { + Vector3g const relative = mainToArea.transform_position({x[i], y[i], z[i]}); + Vector3 const relativeMeters = Vector3(relative) * scale; + + Quaterniond const rot{{qx[i], qy[i], qz[i]}, qw[i]}; + + rDrawShVisual.m_shader + .setTransformationMatrix( + viewProj.m_view + * Matrix4::translation(relativeMeters) + * Matrix4::scaling({200, 200, 200}) + * Matrix4{(mainToAreaRot * Quaternion{rot}).toMatrix()} ) + .draw(rSphereMeshGl); + } + + })); + + return uniTestPlanetsRdr; } +#endif + +} // namespace testapp::scenes diff --git a/src/test_application/activescenes/scene_renderer.h b/src/test_application/activescenes/scene_renderer.h index b724d92a..bb65440f 100644 --- a/src/test_application/activescenes/scene_renderer.h +++ b/src/test_application/activescenes/scene_renderer.h @@ -26,41 +26,115 @@ #include "scenarios.h" -#include "../ActiveApplication.h" +#include + +#include "../MagnumApplication.h" namespace testapp::scenes { +osp::Session setup_window_app( + osp::TopTaskBuilder& rBuilder, + osp::ArrayView topData, + osp::Session const& application); -osp::Session setup_magnum_application( - Builder_t& rBuilder, +osp::Session setup_magnum( + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::TopDataId const idResources, - ActiveApplication::Arguments args); + osp::Session const& windowApp, + osp::Session const& application, + MagnumApplication::Arguments args); /** * @brief Magnum-powered OpenGL Renderer */ osp::Session setup_scene_renderer( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, + osp::Session const& application, + osp::Session const& windowApp, osp::Session const& magnum, osp::Session const& scene, - osp::TopDataId idResources); + osp::Session const& commonScene); /** - * @brief Assign a material from setup_material to use Magnum MeshVisualizer + * @brief Magnum MeshVisualizer shader and optional material for drawing ActiveEnts with it */ osp::Session setup_shader_visualizer( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, + osp::ArrayView topData, + osp::Session const& magnum, + osp::Session const& scene, + osp::Session const& commonScene, + osp::Session const& scnRender, + osp::active::MaterialId materialId = lgrn::id_null()); + +/** + * @brief Magnum Flat shader and optional material for drawing ActiveEnts with it + */ +osp::Session setup_shader_flat( + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, osp::Session const& magnum, osp::Session const& scene, osp::Session const& scnRender, osp::Session const& material); +/** + * @brief Magnum Phong shader and optional material for drawing ActiveEnts with it + */ +osp::Session setup_shader_phong( + osp::TopTaskBuilder& rBuilder, + osp::ArrayView topData, + osp::Session const& magnum, + osp::Session const& scene, + osp::Session const& scnRender, + osp::Session const& material); + +/** + * @brief Red indicators over Magic Rockets + */ +osp::Session setup_thrust_indicators( + osp::TopTaskBuilder& rBuilder, + osp::ArrayView topData, + osp::Session const& magnum, + osp::Session const& commonScene, + osp::Session const& parts, + osp::Session const& signalsFloat, + osp::Session const& scnRender, + osp::Session const& cameraCtrl, + osp::Session const& shFlat, + osp::TopDataId const idResources, + osp::PkgId const pkg); + +/** + * @brief Wireframe cube over the camera controller's target + */ +osp::Session setup_cursor( + osp::TopTaskBuilder& rBuilder, + osp::ArrayView topData, + osp::Session const& magnum, + osp::Session const& commonScene, + osp::Session const& scnRender, + osp::Session const& cameraCtrl, + osp::Session const& shFlat, + osp::TopDataId const idResources, + osp::PkgId const pkg); + +/** + * @brief Draw universe, specifically designed for setup_uni_test_planets + */ +osp::Session setup_uni_test_planets_renderer( + osp::TopTaskBuilder& rBuilder, + osp::ArrayView topData, + osp::Session const& magnum, + osp::Session const& scnRender, + osp::Session const& commonScene, + osp::Session const& cameraCtrl, + osp::Session const& visualizer, + osp::Session const& uniCore, + osp::Session const& uniScnFrame, + osp::Session const& uniTestPlanets); + } diff --git a/src/test_application/activescenes/scene_universe.cpp b/src/test_application/activescenes/scene_universe.cpp new file mode 100644 index 00000000..d859f92c --- /dev/null +++ b/src/test_application/activescenes/scene_universe.cpp @@ -0,0 +1,293 @@ +/** + * Open Space Program + * Copyright © 2019-2022 Open Space Program Project + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "scene_universe.h" + +#include "identifiers.h" + +#include +#include + +#include +#include + +#include + +using namespace osp; +using namespace osp::universe; + +namespace testapp::scenes +{ + +#if 0 + +Session setup_uni_core( + TopTaskBuilder& rBuilder, + ArrayView topData) +{ + Session uniCore; + OSP_SESSION_ACQUIRE_DATA(uniCore, topData, TESTAPP_UNI_CORE); + OSP_SESSION_ACQUIRE_TAGS(uniCore, rTags, TESTAPP_UNI_CORE); + + top_emplace< Universe > (topData, idUniverse); + + return uniCore; +} + + +Session setup_uni_sceneframe( + TopTaskBuilder& rBuilder, + ArrayView topData) +{ + Session scnFrame; + OSP_SESSION_ACQUIRE_DATA(scnFrame, topData, TESTAPP_UNI_SCENEFRAME); + OSP_SESSION_ACQUIRE_TAGS(scnFrame, rTags, TESTAPP_UNI_SCENEFRAME); + + rBuilder.tag(tgScnFramePosReq).depend_on({tgScnFramePosMod}); + + top_emplace< SceneFrame > (topData, idScnFrame); + + return scnFrame; +} + + +Session setup_uni_test_planets( + TopTaskBuilder& rBuilder, + ArrayView topData, + Session const& uniCore, + Session const& scnFrame) +{ + using CoSpaceIdVec_t = std::vector; + using Corrade::Containers::Array; + + OSP_SESSION_UNPACK_TAGS(uniCore, TESTAPP_UNI_CORE); + OSP_SESSION_UNPACK_DATA(uniCore, TESTAPP_UNI_CORE); + OSP_SESSION_UNPACK_TAGS(scnFrame, TESTAPP_UNI_SCENEFRAME); + OSP_SESSION_UNPACK_DATA(scnFrame, TESTAPP_UNI_SCENEFRAME); + + auto &rUniverse = top_get< Universe >(topData, idUniverse); + + constexpr int precision = 10; + constexpr int planetCount = 64; + constexpr int seed = 1337; + constexpr spaceint_t maxDist = math::mul_2pow(20000ul, precision); + constexpr float maxVel = 800.0f; + + // Create coordinate spaces + CoSpaceId const mainSpace = rUniverse.m_coordIds.create(); + std::vector satSurfaceSpaces(planetCount); + rUniverse.m_coordIds.create(satSurfaceSpaces.begin(), satSurfaceSpaces.end()); + + rUniverse.m_coordCommon.resize(rUniverse.m_coordIds.capacity()); + + CoSpaceCommon &rMainSpaceCommon = rUniverse.m_coordCommon[mainSpace]; + rMainSpaceCommon.m_satCount = planetCount; + rMainSpaceCommon.m_satCapacity = planetCount; + + // Associate each planet satellite with their surface coordinate space + for (SatId satId = 0; satId < planetCount; ++satId) + { + CoSpaceId const surfaceSpaceId = satSurfaceSpaces[satId]; + CoSpaceCommon &rCommon = rUniverse.m_coordCommon[surfaceSpaceId]; + rCommon.m_parent = mainSpace; + rCommon.m_parentSat = satId; + } + + // Coordinate space data is a single allocation partitioned to hold positions, velocities, and + // rotations. + // TODO: Alignment is needed for SIMD (not yet implemented). see Corrade alignedAlloc + + std::size_t bytesUsed = 0; + + // Positions and velocities are arranged as XXXX... YYYY... ZZZZ... + partition(bytesUsed, planetCount, rMainSpaceCommon.m_satPositions[0]); + partition(bytesUsed, planetCount, rMainSpaceCommon.m_satPositions[1]); + partition(bytesUsed, planetCount, rMainSpaceCommon.m_satPositions[2]); + partition(bytesUsed, planetCount, rMainSpaceCommon.m_satVelocities[0]); + partition(bytesUsed, planetCount, rMainSpaceCommon.m_satVelocities[1]); + partition(bytesUsed, planetCount, rMainSpaceCommon.m_satVelocities[2]); + + // Rotations use XYZWXYZWXYZWXYZW... + partition(bytesUsed, planetCount, rMainSpaceCommon.m_satRotations[0], + rMainSpaceCommon.m_satRotations[1], + rMainSpaceCommon.m_satRotations[2], + rMainSpaceCommon.m_satRotations[3]); + + // Allocate data for all planets + rMainSpaceCommon.m_data = Array{Corrade::NoInit, bytesUsed}; + + // Create easily accessible array views for each component + auto const [x, y, z] = sat_views(rMainSpaceCommon.m_satPositions, rMainSpaceCommon.m_data, planetCount); + auto const [vx, vy, vz] = sat_views(rMainSpaceCommon.m_satVelocities, rMainSpaceCommon.m_data, planetCount); + auto const [qx, qy, qz, qw] = sat_views(rMainSpaceCommon.m_satRotations, rMainSpaceCommon.m_data, planetCount); + + std::mt19937 gen(seed); + std::uniform_int_distribution posDist(-maxDist, maxDist); + std::uniform_real_distribution velDist(-maxVel, maxVel); + + for (std::size_t i = 0; i < planetCount; ++i) + { + // Assign each planet random positions and velocities + x[i] = posDist(gen); + y[i] = posDist(gen); + z[i] = posDist(gen); + vx[i] = velDist(gen); + vy[i] = velDist(gen); + vz[i] = velDist(gen); + + // No rotation + qx[i] = 0.0; + qy[i] = 0.0; + qz[i] = 0.0; + qw[i] = 1.0; + } + + // Set initial scene frame + + auto &rScnFrame = top_get(topData, idScnFrame); + rScnFrame.m_parent = mainSpace; + rScnFrame.m_position = math::mul_2pow({400, 400, 400}, precision); + + Session uniTestPlanets; + OSP_SESSION_ACQUIRE_DATA(uniTestPlanets, topData, TESTAPP_UNI_PLANETS); + + top_emplace< CoSpaceId > (topData, idPlanetMainSpace, mainSpace); + top_emplace< float > (topData, tgUniDeltaTimeIn, 1.0f / 60.0f); + top_emplace< CoSpaceIdVec_t > (topData, idSatSurfaceSpaces, std::move(satSurfaceSpaces)); + + + uniTestPlanets.task() = rBuilder.task().assign({tgUniTimeEvt, tgScnFramePosReq}).data( + "Update planets", + TopDataIds_t{ idUniverse, idPlanetMainSpace, idScnFrame, idSatSurfaceSpaces, tgUniDeltaTimeIn}, + wrap_args([] (Universe& rUniverse, CoSpaceId const planetMainSpace, SceneFrame &rScnFrame, CoSpaceIdVec_t const& rSatSurfaceSpaces, float const uniDeltaTimeIn) noexcept + { + CoSpaceCommon &rMainSpaceCommon = rUniverse.m_coordCommon[planetMainSpace]; + + float const scale = osp::math::mul_2pow(1.0f, -rMainSpaceCommon.m_precision); + float const scaleDelta = uniDeltaTimeIn / scale; + + auto const [x, y, z] = sat_views(rMainSpaceCommon.m_satPositions, rMainSpaceCommon.m_data, rMainSpaceCommon.m_satCount); + auto const [vx, vy, vz] = sat_views(rMainSpaceCommon.m_satVelocities, rMainSpaceCommon.m_data, rMainSpaceCommon.m_satCount); + auto const [qx, qy, qz, qw] = sat_views(rMainSpaceCommon.m_satRotations, rMainSpaceCommon.m_data, rMainSpaceCommon.m_satCount); + + // Phase 1: Move satellites + + for (std::size_t i = 0; i < rMainSpaceCommon.m_satCount; ++i) + { + x[i] += vx[i] * scaleDelta; + y[i] += vy[i] * scaleDelta; + z[i] += vz[i] * scaleDelta; + + // Apply arbitrary inverse-square gravity towards origin + Vector3d const pos = Vector3d( Vector3g( x[i], y[i], z[i] ) ) * scale; + float const r = pos.length(); + float const c_gm = 10000000000.0f; + Vector3d const accel = -pos * uniDeltaTimeIn * c_gm / (r * r * r); + + vx[i] += accel.x(); + vy[i] += accel.y(); + vz[i] += accel.z(); + + // Rotate based on i, semi-random + Vector3d const axis = Vector3d{std::sin(i), std::cos(i), double(i % 8 - 4)}.normalized(); + Radd const speed{(i % 16) / 16.0}; + + Quaterniond const rot = Quaterniond{{qx[i], qy[i], qz[i]}, qw[i]} + * Quaterniond::rotation(speed * uniDeltaTimeIn, axis); + qx[i] = rot.vector().x(); + qy[i] = rot.vector().y(); + qz[i] = rot.vector().z(); + qw[i] = rot.scalar(); + } + + // Phase 2: Transfers and stuff + + constexpr float captureDist = 500.0f; + + Vector3g const cameraPos{rScnFrame.m_rotation.transformVector(Vector3d(rScnFrame.m_scenePosition))}; + Vector3g const areaPos{rScnFrame.m_position + cameraPos}; + + bool const notInPlanet = (rScnFrame.m_parent == planetMainSpace); + + if (notInPlanet) + { + // Find a planet to enter + std::size_t nearbyPlanet = rMainSpaceCommon.m_satCount; + for (std::size_t i = 0; i < rMainSpaceCommon.m_satCount; ++i) + { + Vector3 const diff = (Vector3( x[i], y[i], z[i] ) - Vector3(areaPos)) * scale; + if (diff.length() < captureDist) + { + nearbyPlanet = i; + break; + } + } + + if (nearbyPlanet < rMainSpaceCommon.m_satCount) + { + OSP_LOG_INFO("Captured into Satellite {} under CoordSpace {}", + nearbyPlanet, int(rSatSurfaceSpaces[nearbyPlanet])); + + CoSpaceId const surface = rSatSurfaceSpaces[nearbyPlanet]; + CoSpaceCommon &rSurfaceCommon = rUniverse.m_coordCommon[surface]; + + CoSpaceTransform const surfaceTf = coord_get_transform(rSurfaceCommon, rSurfaceCommon, x, y, z, qx, qy, qz, qw); + CoordTransformer const mainToSurface = coord_parent_to_child(rMainSpaceCommon, surfaceTf); + + // Transfer scene frame from Main to Surface coordinate space + rScnFrame.m_parent = surface; + rScnFrame.m_position = mainToSurface.transform_position(rScnFrame.m_position); + rScnFrame.m_rotation = mainToSurface.rotation() * rScnFrame.m_rotation; + } + } + else + { + // Currently within planet, try to escape it + + Vector3 const diff = Vector3(areaPos) * scale; + if (diff.length() > captureDist) + { + OSP_LOG_INFO("Leaving planet"); + + CoSpaceId const surface = rScnFrame.m_parent; + CoSpaceCommon &rSurfaceCommon = rUniverse.m_coordCommon[surface]; + + CoSpaceTransform const surfaceTf = coord_get_transform(rSurfaceCommon, rSurfaceCommon, x, y, z, qx, qy, qz, qw); + CoordTransformer const surfaceToMain = coord_child_to_parent(rMainSpaceCommon, surfaceTf); + + // Transfer scene frame from Surface to Main coordinate space + rScnFrame.m_parent = planetMainSpace; + rScnFrame.m_position = surfaceToMain.transform_position(rScnFrame.m_position); + rScnFrame.m_rotation = surfaceToMain.rotation() * rScnFrame.m_rotation; + } + } + })); + + return uniTestPlanets; +} + +#endif + +} // namespace testapp::scenes diff --git a/src/test_application/activescenes/scene_universe.h b/src/test_application/activescenes/scene_universe.h new file mode 100644 index 00000000..4b912c40 --- /dev/null +++ b/src/test_application/activescenes/scene_universe.h @@ -0,0 +1,56 @@ +/** + * Open Space Program + * Copyright © 2019-2022 Open Space Program Project + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#pragma once + +#include "scenarios.h" + +namespace testapp::scenes +{ + +/** + * @brief Core Universe struct with addressable Coordinate Spaces + */ +osp::Session setup_uni_core( + osp::TopTaskBuilder& rBuilder, + osp::ArrayView topData); + +/** + * @brief Represents the physics scene's presence in a Universe + */ +osp::Session setup_uni_sceneframe( + osp::TopTaskBuilder& rBuilder, + osp::ArrayView topData); + +/** + * @brief Unrealistic planets test, allows SceneFrame to move around and get captured into planets + */ +osp::Session setup_uni_test_planets( + osp::TopTaskBuilder& rBuilder, + osp::ArrayView topData, + osp::Session const& uniCore, + osp::Session const& uniScnFrame); + + +} diff --git a/src/test_application/activescenes/scene_vehicles.cpp b/src/test_application/activescenes/scene_vehicles.cpp index 2497b238..5b5d862b 100644 --- a/src/test_application/activescenes/scene_vehicles.cpp +++ b/src/test_application/activescenes/scene_vehicles.cpp @@ -51,15 +51,15 @@ using namespace Magnum::Math::Literals; namespace testapp::scenes { +#if 0 Session setup_parts( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& scnCommon, + Session const& commonScene, TopDataId const idResources) { - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); Session parts; OSP_SESSION_ACQUIRE_DATA(parts, topData, TESTAPP_PARTS); @@ -141,13 +141,12 @@ Session setup_parts( } Session setup_signals_float( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& scnCommon, + Session const& commonScene, Session const& parts) { - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); OSP_SESSION_UNPACK_DATA(parts, TESTAPP_PARTS); OSP_SESSION_UNPACK_TAGS(parts, TESTAPP_PARTS); @@ -238,14 +237,13 @@ TopTaskFunc_t gen_allocate_mach_bitsets() } Session setup_mach_rocket( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& scnCommon, + Session const& commonScene, Session const& parts, Session const& signalsFloat) { - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); OSP_SESSION_UNPACK_DATA(signalsFloat, TESTAPP_SIGNALS_FLOAT) OSP_SESSION_UNPACK_TAGS(signalsFloat, TESTAPP_SIGNALS_FLOAT); OSP_SESSION_UNPACK_DATA(parts, TESTAPP_PARTS); @@ -267,14 +265,13 @@ Session setup_mach_rocket( } Session setup_mach_rcsdriver( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& scnCommon, + Session const& commonScene, Session const& parts, Session const& signalsFloat) { - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); OSP_SESSION_UNPACK_DATA(signalsFloat, TESTAPP_SIGNALS_FLOAT) OSP_SESSION_UNPACK_TAGS(signalsFloat, TESTAPP_SIGNALS_FLOAT); OSP_SESSION_UNPACK_DATA(parts, TESTAPP_PARTS); @@ -357,12 +354,11 @@ Session setup_mach_rcsdriver( } Session setup_vehicle_spawn( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& scnCommon) + Session const& commonScene) { - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); Session vehicleSpawn; OSP_SESSION_ACQUIRE_DATA(vehicleSpawn, topData, TESTAPP_VEHICLE_SPAWN); @@ -389,18 +385,17 @@ Session setup_vehicle_spawn( } Session setup_vehicle_spawn_vb( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - [[maybe_unused]] Tags& rTags, - Session const& scnCommon, + Session const& commonScene, Session const& prefabs, Session const& parts, Session const& vehicleSpawn, Session const& signalsFloat, TopDataId const idResources) { - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(commonScene, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); OSP_SESSION_UNPACK_DATA(prefabs, TESTAPP_PREFABS); OSP_SESSION_UNPACK_TAGS(prefabs, TESTAPP_PREFABS); OSP_SESSION_UNPACK_DATA(vehicleSpawn, TESTAPP_VEHICLE_SPAWN); @@ -961,13 +956,12 @@ void add_rcs_block(VehicleBuilder& rBuilder, VehicleBuilder::WeldVec_t& rWeldTo, } Session setup_test_vehicles( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - [[maybe_unused]] Tags& rTags, - Session const& scnCommon, + Session const& commonScene, TopDataId const idResources) { - OSP_SESSION_UNPACK_TAGS(scnCommon, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_TAGS(commonScene, TESTAPP_COMMON_SCENE); Session testVehicles; OSP_SESSION_ACQUIRE_DATA(testVehicles, topData, TESTAPP_TEST_VEHICLES); @@ -1078,15 +1072,14 @@ struct VehicleTestControls }; Session setup_vehicle_control( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, ArrayView const topData, - Tags& rTags, - Session const& scnCommon, + Session const& commonScene, Session const& parts, Session const& signalsFloat, Session const& app) { - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(commonScene, TESTAPP_COMMON_SCENE); OSP_SESSION_UNPACK_DATA(signalsFloat, TESTAPP_SIGNALS_FLOAT) OSP_SESSION_UNPACK_TAGS(signalsFloat, TESTAPP_SIGNALS_FLOAT); OSP_SESSION_UNPACK_DATA(parts, TESTAPP_PARTS); @@ -1211,17 +1204,16 @@ Session setup_vehicle_control( } Session setup_camera_vehicle( - Builder_t& rBuilder, + TopTaskBuilder& rBuilder, [[maybe_unused]] ArrayView const topData, - [[maybe_unused]] Tags& rTags, Session const& app, - Session const& scnCommon, + Session const& commonScene, Session const& parts, Session const& physics, Session const& camera, Session const& vehicleControl) { - OSP_SESSION_UNPACK_DATA(scnCommon, TESTAPP_COMMON_SCENE); + OSP_SESSION_UNPACK_DATA(commonScene, TESTAPP_COMMON_SCENE); OSP_SESSION_UNPACK_DATA(parts, TESTAPP_PARTS); OSP_SESSION_UNPACK_TAGS(physics, TESTAPP_PHYSICS); OSP_SESSION_UNPACK_TAGS(app, TESTAPP_APP); @@ -1269,4 +1261,6 @@ Session setup_camera_vehicle( return cameraFree; } +#endif + } // namespace testapp::scenes diff --git a/src/test_application/activescenes/scene_vehicles.h b/src/test_application/activescenes/scene_vehicles.h index f24063e8..5ea998e5 100644 --- a/src/test_application/activescenes/scene_vehicles.h +++ b/src/test_application/activescenes/scene_vehicles.h @@ -31,16 +31,13 @@ namespace testapp { struct VehicleData; } namespace testapp::scenes { -using MachTypeToEvt_t = std::vector; - /** * @brief Support for Parts, Machines, and Links */ osp::Session setup_parts( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& commonScene, osp::TopDataId const idResources); /** @@ -73,10 +70,9 @@ osp::Session setup_parts( * another float signal, all running within a single frame. */ osp::Session setup_signals_float( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& commonScene, osp::Session const& parts); /** @@ -85,10 +81,9 @@ osp::Session setup_signals_float( * This only sets up links and does not apply forces, see setup_rocket_thrust_newton */ osp::Session setup_mach_rocket( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& commonScene, osp::Session const& parts, osp::Session const& signalsFloat); @@ -96,10 +91,9 @@ osp::Session setup_mach_rocket( * @brief Links for RCS Drivers, which output thrust levels given pitch/yaw/roll controls */ osp::Session setup_mach_rcsdriver( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& commonScene, osp::Session const& parts, osp::Session const& signalsFloat); @@ -110,19 +104,17 @@ osp::Session setup_mach_rcsdriver( * of conencted Parts */ osp::Session setup_vehicle_spawn( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon); + osp::Session const& commonScene); /** * @brief Support VehicleBuilder data to be used to spawn vehicles */ osp::Session setup_vehicle_spawn_vb( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& commonScene, osp::Session const& prefabs, osp::Session const& parts, osp::Session const& vehicleSpawn, @@ -133,20 +125,18 @@ osp::Session setup_vehicle_spawn_vb( * @brief Build "Test Vehicle" data, so they can be spawned */ osp::Session setup_test_vehicles( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& commonScene, osp::TopDataId const idResources); /** * @brief Controls to select and control a UserControl Machine */ osp::Session setup_vehicle_control( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, - osp::Session const& scnCommon, + osp::Session const& commonScene, osp::Session const& parts, osp::Session const& signalsFloat, osp::Session const& app); @@ -155,11 +145,10 @@ osp::Session setup_vehicle_control( * @brief Camera which can free cam or follow a selected vehicle */ osp::Session setup_camera_vehicle( - Builder_t& rBuilder, + osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Tags& rTags, osp::Session const& app, - osp::Session const& scnCommon, + osp::Session const& commonScene, osp::Session const& parts, osp::Session const& physics, osp::Session const& camera, diff --git a/src/test_application/executor.cpp b/src/test_application/executor.cpp new file mode 100644 index 00000000..09ae3128 --- /dev/null +++ b/src/test_application/executor.cpp @@ -0,0 +1,76 @@ +/** + * Open Space Program + * Copyright © 2019-2023 Open Space Program Project + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "executor.h" + +#include + +#include + +namespace testapp +{ + +void SingleThreadedExecutor::load(TestAppTasks& rAppTasks) +{ + osp::exec_conform(rAppTasks.m_tasks, m_execContext); + m_execContext.doLogging = m_log != nullptr; +} + +void SingleThreadedExecutor::run(TestAppTasks& rAppTasks, osp::PipelineId pipeline) +{ + osp::exec_request_run(m_execContext, pipeline); +} + +void SingleThreadedExecutor::signal(TestAppTasks& rAppTasks, osp::PipelineId pipeline) +{ + osp::exec_signal(m_execContext, pipeline); +} + +void SingleThreadedExecutor::wait(TestAppTasks& rAppTasks) +{ + if (m_log != nullptr) + { + m_log->info("\n>>>>>>>>>> Previous State Changes\n{}\n>>>>>>>>>> Current State\n{}\n", + osp::TopExecWriteLog {rAppTasks.m_tasks, rAppTasks.m_taskData, rAppTasks.m_graph, m_execContext}, + osp::TopExecWriteState{rAppTasks.m_tasks, rAppTasks.m_taskData, rAppTasks.m_graph, m_execContext} ); + m_execContext.logMsg.clear(); + } + + osp::exec_update(rAppTasks.m_tasks, rAppTasks.m_graph, m_execContext); + osp::top_run_blocking(rAppTasks.m_tasks, rAppTasks.m_graph, rAppTasks.m_taskData, rAppTasks.m_topData, m_execContext); + + if (m_log != nullptr) + { + m_log->info("\n>>>>>>>>>> New State Changes\n{}", + osp::TopExecWriteLog{rAppTasks.m_tasks, rAppTasks.m_taskData, rAppTasks.m_graph, m_execContext} ); + m_execContext.logMsg.clear(); + } +} + +bool SingleThreadedExecutor::is_running(TestAppTasks const& appTasks) +{ + return m_execContext.hasRequestRun || (m_execContext.pipelinesRunning != 0); +} + +} // namespace testapp diff --git a/src/test_application/executor.h b/src/test_application/executor.h new file mode 100644 index 00000000..9099b50d --- /dev/null +++ b/src/test_application/executor.h @@ -0,0 +1,54 @@ +/** + * Open Space Program + * Copyright © 2019-2023 Open Space Program Project + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#pragma once + +#include "testapp.h" + +#include + +#include + +namespace testapp +{ + +struct SingleThreadedExecutor final : public IExecutor +{ + void load(TestAppTasks& rAppTasks) override; + + void run(TestAppTasks& rAppTasks, osp::PipelineId pipeline) override; + + void signal(TestAppTasks& rAppTasks, osp::PipelineId pipeline) override; + + void wait(TestAppTasks& rAppTasks) override; + + bool is_running(TestAppTasks const& rAppTasks) override; + + osp::ExecContext m_execContext; + + std::shared_ptr m_log; +}; + +} // namespace testapp + diff --git a/src/test_application/main.cpp b/src/test_application/main.cpp index 45f6f1df..73ecb837 100644 --- a/src/test_application/main.cpp +++ b/src/test_application/main.cpp @@ -22,11 +22,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +#include "MagnumApplication.h" +#include "executor.h" +#include "testapp.h" -#include "osp/tasks/top_utils.h" -#include - -#include "ActiveApplication.h" #include "activescenes/scenarios.h" #include "activescenes/identifiers.h" #include "activescenes/scene_renderer.h" @@ -35,15 +34,13 @@ #include #include -#include -#include #include -#include #include #include #include +#include #include #include #include @@ -58,10 +55,14 @@ #include +#include + #include +#include #include #include #include + #include #include @@ -71,13 +72,11 @@ using namespace testapp; * @brief Starts a spaghetti REPL line interface that gets inputs from stdin * * This interface can be used to run commands and load scenes - * - * @return An error code maybe */ -int debug_cli_loop(); +void debug_cli_loop(); /** - * @brief Starts Magnum application (ActiveApplication) thread g_magnumThread + * @brief Starts Magnum application (MagnumApplication) thread g_magnumThread * * This initializes an OpenGL context, and opens the window */ @@ -89,97 +88,39 @@ void start_magnum_async(); * This should only be called once for the entire lifetime * of the program. * - * prefer not to use names like this anywhere else but main.cpp + * prefer not to use names like this outside of testapp */ void load_a_bunch_of_stuff(); -/** - * @brief Deal with resource reference counts for a clean termination - */ -void clear_resource_owners(); - // called only from commands to display information void debug_print_help(); void debug_print_resources(); -// TopData stores most application state, addressed using a TopDataId -std::vector g_appTopData; - -// TopTasks and are organized with Tags to form task graphs and events. -// Each TopTask is given a vector of TopDataIds its allowed to access -osp::Tasks g_tasks; -osp::TopTaskDataVec_t g_taskData; -osp::Tags g_tags; +TestApp g_testApp; -// Current execution state of TopTasks -// g_tasks, g_taskData, and g_tags stay constant during execution -osp::ExecutionContext g_exec; +SingleThreadedExecutor g_executor; -// Sessions bundle together and own TopDataIds, TopTaskIds, and TagsIds -// Sessions intend to add support for something to exist in the world -// eg, Adding support for physics or supporting a certain shader - -// Application Session lasts the lifetime of the program. -// It stores the Resources class, addressable by a TopDataId -osp::Session g_application; -osp::TopDataId g_idResources{lgrn::id_null()}; - -// Default Package in Resources -osp::PkgId g_defaultPkg; - -// Sessions that make up the current scene -osp::Sessions_t g_sceneSessions; - -// Sessions for rendering. These only exist when the Magnum Application is open -osp::Session g_magnum; -osp::Sessions_t g_renderSessions; - -// Magnum Application deals with window and OpenGL things -std::thread g_magnumThread; - -// Called when openning a Magnum Application -RendererSetup_t g_rendererSetup; +std::thread g_magnumThread; // Loggers std::shared_ptr g_logTestApp; +std::shared_ptr g_logExecutor; std::shared_ptr g_logMagnumApp; // lazily save the arguments to pass to Magnum int g_argc; char** g_argv; -static void close_sessions(osp::Sessions_t &rSessions) -{ - osp::top_close_session(g_tags, g_tasks, g_taskData, g_appTopData, g_exec, rSessions); - rSessions.clear(); -} - -static void close_session(osp::Session &rSession) -{ - osp::top_close_session(g_tags, g_tasks, g_taskData, g_appTopData, g_exec, osp::ArrayView(&rSession, 1)); -} - -static MainView get_main_view() -{ - return { - .m_topData = g_appTopData, - .m_rTags = g_tags, - .m_rTasks = g_tasks, - .m_rExec = g_exec, - .m_rTaskData = g_taskData, - .m_idResources = g_idResources, - }; -} - int main(int argc, char** argv) { Corrade::Utility::Arguments args; args.addSkippedPrefix("magnum", "Magnum options") - .addOption("scene", "none").setHelp("scene", "Set the scene to launch") - .addOption("config").setHelp("config", "path to configuration file to use") - .addBooleanOption("norepl").setHelp("norepl", "don't enter read, evaluate, print, loop.") - .addBooleanOption('v', "verbose").setHelp("verbose", "log verbosely") + .addOption("scene", "none") .setHelp("scene", "Set the scene to launch") + .addOption("config") .setHelp("config", "path to configuration file to use") + .addBooleanOption("norepl") .setHelp("norepl", "don't enter read, evaluate, print, loop.") + .addBooleanOption("log-exec") .setHelp("log-exec", "Log Task/Pipeline Execution (Extremely chatty!)") + // TODO .addBooleanOption('v', "verbose") .setHelp("verbose", "log verbosely") .setGlobalHelp("Helptext goes here.") .parse(argc, argv); @@ -191,13 +132,22 @@ int main(int argc, char** argv) { auto pSink = std::make_shared(); pSink->set_pattern("[%T.%e] [%n] [%^%l%$] [%s:%#] %v"); - g_logTestApp = std::make_shared("testapp", pSink); + g_logTestApp = std::make_shared("testapp", pSink); + g_logExecutor = std::make_shared("executor", pSink); g_logMagnumApp = std::make_shared("flight", std::move(pSink)); } - osp::set_thread_logger(g_logTestApp); // Set logger for this thread + // Set thread-local logger used by OSP_LOG_* macros + osp::set_thread_logger(g_logTestApp); + + g_testApp.m_pExecutor = &g_executor; + + if (args.isSet("log-exec")) + { + g_executor.m_log = g_logExecutor; + } - g_appTopData.resize(64); + g_testApp.m_topData.resize(64); load_a_bunch_of_stuff(); if(args.value("scene") != "none") @@ -206,11 +156,11 @@ int main(int argc, char** argv) if(it == std::end(scenarios())) { std::cerr << "unknown scene" << std::endl; - clear_resource_owners(); + g_testApp.clear_resource_owners(); exit(-1); } - g_rendererSetup = it->second.m_setup(get_main_view(), g_defaultPkg, g_sceneSessions); + g_testApp.m_rendererSetup = it->second.m_setup(g_testApp); start_magnum_async(); } @@ -232,7 +182,7 @@ int main(int argc, char** argv) return 0; } -int debug_cli_loop() +void debug_cli_loop() { debug_print_help(); @@ -243,7 +193,7 @@ int debug_cli_loop() std::cout << "> "; std::cin >> command; - bool magnumOpen = ! g_magnum.m_dataIds.empty(); + bool magnumOpen = ! g_testApp.m_renderer.m_sessions.empty(); if (auto const it = scenarios().find(command); it != std::end(scenarios())) { @@ -259,9 +209,15 @@ int debug_cli_loop() { std::cout << "Loading scene: " << it->first << "\n"; - close_sessions(g_sceneSessions); // Close existing scene + // Close existing scene first + if ( ! g_testApp.m_scene.m_sessions.empty() ) + { + g_testApp.close_sessions(g_testApp.m_scene.m_sessions); + g_testApp.m_scene.m_sessions.clear(); + g_testApp.m_scene.m_edges.m_syncWith.clear(); + } - g_rendererSetup = it->second.m_setup(get_main_view(), g_defaultPkg, g_sceneSessions); + g_testApp.m_rendererSetup = it->second.m_setup(g_testApp); start_magnum_async(); } } @@ -275,7 +231,7 @@ int debug_cli_loop() { std::cout << "Application is already open\n"; } - else if ( ! bool(g_rendererSetup) ) + else if ( g_testApp.m_rendererSetup == nullptr ) { std::cout << "No existing scene loaded\n"; } @@ -294,8 +250,8 @@ int debug_cli_loop() if (magnumOpen) { // Request exit if application exists - OSP_SESSION_UNPACK_DATA(g_magnum, TESTAPP_APP_MAGNUM); // declares idActiveApp - osp::top_get(g_appTopData, idActiveApp).exit(); + OSP_DECLARE_GET_DATA_IDS(g_testApp.m_renderer.m_sessions[1], TESTAPP_DATA_MAGNUM); // declares idActiveApp + osp::top_get(g_testApp.m_topData, idActiveApp).exit(); } break; @@ -306,9 +262,7 @@ int debug_cli_loop() } } - clear_resource_owners(); - - return 0; + g_testApp.clear_resource_owners(); } void start_magnum_async() @@ -322,25 +276,36 @@ void start_magnum_async() osp::set_thread_logger(g_logMagnumApp); // Start Magnum application session - Builder_t builder{g_tags, g_tasks, g_taskData}; - g_magnum = scenes::setup_magnum_application(builder, g_appTopData, g_tags, g_idResources, {g_argc, g_argv}); - OSP_SESSION_UNPACK_DATA(g_magnum, TESTAPP_APP_MAGNUM); // declares idActiveApp - auto &rActiveApp = osp::top_get(g_appTopData, idActiveApp); + osp::TopTaskBuilder builder{g_testApp.m_tasks, g_testApp.m_renderer.m_edges, g_testApp.m_taskData}; + + g_testApp.m_windowApp = scenes::setup_window_app (builder, g_testApp.m_topData, g_testApp.m_application); + g_testApp.m_magnum = scenes::setup_magnum (builder, g_testApp.m_topData, g_testApp.m_windowApp, g_testApp.m_application, {g_argc, g_argv}); + + OSP_DECLARE_GET_DATA_IDS(g_testApp.m_magnum, TESTAPP_DATA_MAGNUM); // declares idActiveApp + auto &rActiveApp = osp::top_get(g_testApp.m_topData, idActiveApp); // Setup renderer sessions - g_rendererSetup(get_main_view(), g_magnum, g_sceneSessions, g_renderSessions); + + g_testApp.m_rendererSetup(g_testApp); + + g_testApp.m_graph = osp::make_exec_graph(g_testApp.m_tasks, {&g_testApp.m_renderer.m_edges, &g_testApp.m_scene.m_edges}); + g_executor.load(g_testApp); // Starts the main loop. This function is blocking, and will only return - // once the window is closed. See ActiveApplication::drawEvent + // once the window is closed. See MagnumApplication::drawEvent rActiveApp.exec(); // Destruct draw function lambda first // EngineTest stores the entire renderer in here (if it's active) - rActiveApp.set_on_draw({}); + rActiveApp.set_osp_app({}); // Closing sessions will delete their associated TopData and Tags - close_sessions(g_renderSessions); - close_session(g_magnum); + g_testApp.close_sessions(g_testApp.m_renderer.m_sessions); + g_testApp.m_renderer.m_sessions.clear(); + g_testApp.m_renderer.m_edges.m_syncWith.clear(); + + g_testApp.close_session(g_testApp.m_magnum); + g_testApp.close_session(g_testApp.m_windowApp); OSP_LOG_INFO("Closed Magnum Application"); }); @@ -353,22 +318,43 @@ void load_a_bunch_of_stuff() { using namespace osp::restypes; using namespace Magnum; + using Primitives::ConeFlag; using Primitives::CylinderFlag; std::size_t const maxTags = 256; // aka: just two 64-bit integers std::size_t const maxTagsInts = maxTags / 64; - g_tags.m_tags.reserve(maxTags); - g_tags.m_tagDepends.resize(maxTags * g_tags.m_tagDependsPerTag, - lgrn::id_null()); - g_tags.m_tagLimits.resize(maxTagsInts); - g_tags.m_tagExtern.resize(maxTagsInts); + osp::TopTaskBuilder builder{g_testApp.m_tasks, g_testApp.m_applicationGroup.m_edges, g_testApp.m_taskData}; + auto const plApp = g_testApp.m_application.create_pipelines(builder); + builder.pipeline(plApp.mainLoop).loops(true).wait_for_signal(EStgOptn::ModifyOrSignal); - auto const [idResources] = g_application.acquire_data<1>(g_appTopData); - g_idResources = idResources; + // declares idResources and idMainLoopCtrl + OSP_DECLARE_CREATE_DATA_IDS(g_testApp.m_application, g_testApp.m_topData, TESTAPP_DATA_APPLICATION); + + auto &rResources = osp::top_emplace (g_testApp.m_topData, idResources); + /* unused */ osp::top_emplace(g_testApp.m_topData, idMainLoopCtrl); + + builder.task() + .name ("Schedule Main Loop") + .schedules ({plApp.mainLoop(EStgOptn::Schedule)}) + .push_to (g_testApp.m_application.m_tasks) + .args ({ idMainLoopCtrl}) + .func([] (MainLoopControl const& rMainLoopCtrl) noexcept -> osp::TaskActions + { + if ( ! rMainLoopCtrl.doUpdate + && ! rMainLoopCtrl.doSync + && ! rMainLoopCtrl.doResync + && ! rMainLoopCtrl.doRender) + { + return osp::TaskAction::Cancel; + } + else + { + return { }; + } + }); - auto &rResources = osp::top_emplace(g_appTopData, idResources); rResources.resize_types(osp::ResTypeIdReg_t::size()); @@ -379,7 +365,7 @@ void load_a_bunch_of_stuff() rResources.data_register(gc_importer); rResources.data_register(gc_importer); osp::register_tinygltf_resources(rResources); - g_defaultPkg = rResources.pkg_create(); + g_testApp.m_defaultPkg = rResources.pkg_create(); // Load sturdy glTF files const std::string_view datapath = {"OSPData/adera/"}; @@ -399,86 +385,30 @@ void load_a_bunch_of_stuff() // images, textures, and other relevant data into osp::Resources for (auto const& meshName : meshes) { - osp::ResId res = osp::load_tinygltf_file(osp::string_concat(datapath, meshName), rResources, g_defaultPkg); + osp::ResId res = osp::load_tinygltf_file(osp::string_concat(datapath, meshName), rResources, g_testApp.m_defaultPkg); osp::assigns_prefabs_tinygltf(rResources, res); } // Add a default primitives auto const add_mesh_quick = [&rResources = rResources] (std::string_view const name, Trade::MeshData&& data) { - osp::ResId const meshId = rResources.create(gc_mesh, g_defaultPkg, osp::SharedString::create(name)); + osp::ResId const meshId = rResources.create(gc_mesh, g_testApp.m_defaultPkg, osp::SharedString::create(name)); rResources.data_add(gc_mesh, meshId, std::move(data)); }; Trade::MeshData &&cylinder = Magnum::MeshTools::transform3D( Primitives::cylinderSolid(3, 16, 1.0f, CylinderFlag::CapEnds), Matrix4::rotationX(Deg(90)), 0); + Trade::MeshData &&cone = Magnum::MeshTools::transform3D( Primitives::coneSolid(3, 16, 1.0f, ConeFlag::CapEnd), Matrix4::rotationX(Deg(90)), 0); add_mesh_quick("cube", Primitives::cubeSolid()); + add_mesh_quick("cubewire", Primitives::cubeWireframe()); add_mesh_quick("sphere", Primitives::icosphereSolid(2)); add_mesh_quick("cylinder", std::move(cylinder)); + add_mesh_quick("cone", std::move(cone)); add_mesh_quick("grid64solid", Primitives::grid3DSolid({63, 63})); OSP_LOG_INFO("Resource loading complete"); } -using each_res_id_t = void(*)(osp::ResId); - -static void resource_for_each_type(osp::ResTypeId const type, each_res_id_t const do_thing) -{ - auto &rResources = osp::top_get(g_appTopData, g_idResources); - lgrn::IdRegistry const &rReg = rResources.ids(type); - for (std::size_t i = 0; i < rReg.capacity(); ++i) - { - if (rReg.exists(osp::ResId(i))) - { - do_thing(osp::ResId(i)); - } - } -} - -void clear_resource_owners() -{ - using namespace osp::restypes; - - // Texture resources contain osp::TextureImgSource, which refererence counts - // their associated image data - resource_for_each_type(gc_texture, [] (osp::ResId const id) - { - auto &rResources = osp::top_get(g_appTopData, g_idResources); - - auto * const pData = rResources - .data_try_get(gc_texture, id); - if (pData != nullptr) - { - rResources.owner_destroy(gc_image, std::move(*pData)); - } - }); - - // Importer data own a lot of other resources - resource_for_each_type(gc_importer, [] (osp::ResId const id) - { - auto &rResources = osp::top_get(g_appTopData, g_idResources); - - auto * const pData = rResources - .data_try_get(gc_importer, id); - if (pData != nullptr) - { - for (osp::ResIdOwner_t &rOwner : std::move(pData->m_images)) - { - rResources.owner_destroy(gc_image, std::move(rOwner)); - } - - for (osp::ResIdOwner_t &rOwner : std::move(pData->m_textures)) - { - rResources.owner_destroy(gc_texture, std::move(rOwner)); - } - - for (osp::ResIdOwner_t &rOwner : std::move(pData->m_meshes)) - { - rResources.owner_destroy(gc_mesh, std::move(rOwner)); - } - } - }); -} //----------------------------------------------------------------------------- diff --git a/src/test_application/testapp.cpp b/src/test_application/testapp.cpp new file mode 100644 index 00000000..753623be --- /dev/null +++ b/src/test_application/testapp.cpp @@ -0,0 +1,153 @@ +/** + * Open Space Program + * Copyright © 2019-2023 Open Space Program Project + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "testapp.h" + +#include "activescenes/identifiers.h" + +#include +#include + +#include + +namespace testapp +{ + +void TestApp::close_sessions(osp::ArrayView const sessions) +{ + using namespace osp; + + // Run cleanup pipelines + for (Session &rSession : sessions) + { + if (rSession.m_cleanup != lgrn::id_null()) + { + m_pExecutor->run(*this, rSession.m_cleanup); + } + } + m_pExecutor->wait(*this); + + // Clear each session's TopData + for (Session &rSession : sessions) + { + for (TopDataId const id : std::exchange(rSession.m_data, {})) + { + if (id != lgrn::id_null()) + { + m_topData[std::size_t(id)].reset(); + } + } + } + + // Clear each session's tasks and pipelines + for (Session &rSession : sessions) + { + for (TaskId const task : rSession.m_tasks) + { + m_tasks.m_taskIds.remove(task); + + TopTask &rCurrTaskData = m_taskData[task]; + rCurrTaskData.m_debugName.clear(); + rCurrTaskData.m_dataUsed.clear(); + rCurrTaskData.m_func = nullptr; + } + rSession.m_tasks.clear(); + + for (PipelineId const pipeline : rSession.m_pipelines) + { + m_tasks.m_pipelineIds.remove(pipeline); + m_tasks.m_pipelineParents[pipeline] = lgrn::id_null(); + m_tasks.m_pipelineInfo[pipeline] = {}; + m_tasks.m_pipelineControl[pipeline] = {}; + } + rSession.m_pipelines.clear(); + } +} + + +void TestApp::close_session(osp::Session &rSession) +{ + close_sessions(osp::ArrayView(&rSession, 1)); +} + + +template +static void resource_for_each_type(osp::ResTypeId const type, osp::Resources& rResources, FUNC_T&& do_thing) +{ + lgrn::IdRegistry const &rReg = rResources.ids(type); + for (std::size_t i = 0; i < rReg.capacity(); ++i) + { + if (rReg.exists(osp::ResId(i))) + { + do_thing(osp::ResId(i)); + } + } +} + + +void TestApp::clear_resource_owners() +{ + using namespace osp::restypes; + + // declares idResources + OSP_DECLARE_CREATE_DATA_IDS(m_application, m_topData, TESTAPP_DATA_APPLICATION); + + auto &rResources = osp::top_get(m_topData, idResources); + + // Texture resources contain osp::TextureImgSource, which refererence counts + // their associated image data + resource_for_each_type(gc_texture, rResources, [&rResources] (osp::ResId const id) + { + auto * const pData = rResources.data_try_get(gc_texture, id); + if (pData != nullptr) + { + rResources.owner_destroy(gc_image, std::move(*pData)); + } + }); + + // Importer data own a lot of other resources + resource_for_each_type(gc_importer, rResources, [&rResources] (osp::ResId const id) + { + auto * const pData = rResources.data_try_get(gc_importer, id); + if (pData != nullptr) + { + for (osp::ResIdOwner_t &rOwner : std::move(pData->m_images)) + { + rResources.owner_destroy(gc_image, std::move(rOwner)); + } + + for (osp::ResIdOwner_t &rOwner : std::move(pData->m_textures)) + { + rResources.owner_destroy(gc_texture, std::move(rOwner)); + } + + for (osp::ResIdOwner_t &rOwner : std::move(pData->m_meshes)) + { + rResources.owner_destroy(gc_mesh, std::move(rOwner)); + } + } + }); +} + +} // namespace testapp diff --git a/src/test_application/testapp.h b/src/test_application/testapp.h new file mode 100644 index 00000000..794ce1d5 --- /dev/null +++ b/src/test_application/testapp.h @@ -0,0 +1,95 @@ +/** + * Open Space Program + * Copyright © 2019-2023 Open Space Program Project + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#pragma once + +#include + +#include + +#include + +#include +#include + +#include + +namespace testapp +{ + +struct TestApp; + +using RendererSetupFunc_t = void(*)(TestApp&); +using SceneSetupFunc_t = RendererSetupFunc_t(*)(TestApp&); + +struct TestAppTasks +{ + std::vector m_topData; + osp::Tasks m_tasks; + osp::TopTaskDataVec_t m_taskData; + osp::TaskGraph m_graph; +}; + +struct IExecutor +{ + virtual void load(TestAppTasks& rAppTasks) = 0; + + virtual void run(TestAppTasks& rAppTasks, osp::PipelineId pipeline) = 0; + + virtual void signal(TestAppTasks& rAppTasks, osp::PipelineId pipeline) = 0; + + virtual void wait(TestAppTasks& rAppTasks) = 0; + + virtual bool is_running(TestAppTasks const& rAppTasks) = 0; +}; + +struct TestApp : TestAppTasks +{ + void close_sessions(osp::ArrayView sessions); + + void close_session(osp::Session &rSession); + + /** + * @brief Deal with resource reference counts for a clean termination + */ + void clear_resource_owners(); + + osp::SessionGroup m_applicationGroup; + osp::Session m_application; + + osp::SessionGroup m_scene; + + osp::Session m_windowApp; + osp::Session m_magnum; + osp::SessionGroup m_renderer; + + RendererSetupFunc_t m_rendererSetup { nullptr }; + + IExecutor *m_pExecutor { nullptr }; + + osp::PkgId m_defaultPkg { lgrn::id_null() }; +}; + + +} // namespace testapp diff --git a/test/tasks/CMakeLists.txt b/test/tasks/CMakeLists.txt index a7921cc9..aa4d056a 100644 --- a/test/tasks/CMakeLists.txt +++ b/test/tasks/CMakeLists.txt @@ -26,5 +26,4 @@ PROJECT(test_tasks CXX) ADD_TEST_DIRECTORY(${PROJECT_NAME}) TARGET_LINK_LIBRARIES(test_tasks PRIVATE longeron EnTT::EnTT Magnum::Magnum) -TARGET_SOURCES(test_tasks PRIVATE "${CMAKE_SOURCE_DIR}/src/osp/tasks/execute_simple.cpp") - +TARGET_SOURCES(test_tasks PRIVATE "${CMAKE_SOURCE_DIR}/src/osp/tasks/tasks.cpp" "${CMAKE_SOURCE_DIR}/src/osp/tasks/execute.cpp") diff --git a/test/tasks/main.cpp b/test/tasks/main.cpp index e1c68a71..4d4328db 100644 --- a/test/tasks/main.cpp +++ b/test/tasks/main.cpp @@ -24,160 +24,705 @@ */ #include #include -#include +#include #include #include +#include #include #include using namespace osp; +template +bool contains(RANGE_T const& range, VALUE_T const& value) noexcept +{ + for (auto const& element : range) + { + if (element == value) + { + return true; + } + } + return false; +} + +template +void randomized_singlethreaded_execute(Tasks const& tasks, TaskGraph const& graph, ExecContext& rExec, std::mt19937 &rRand, int maxRuns, RUN_TASK_T && runTask) +{ + for (int i = 0; i < maxRuns; ++i) + { + auto const runTasksLeft = rExec.tasksQueuedRun.size(); + auto const blockedTasksLeft = rExec.tasksQueuedBlocked.size(); + + if (runTasksLeft+blockedTasksLeft == 0) + { + break; + } + + if (runTasksLeft != 0) + { + TaskId const randomTask = rExec.tasksQueuedRun.at(rRand() % runTasksLeft); + TaskActions const status = runTask(randomTask); + complete_task(tasks, graph, rExec, randomTask, status); + } + + exec_update(tasks, graph, rExec); + } +} + +//----------------------------------------------------------------------------- + +namespace test_a +{ + +enum class Stages { Fill, Use, Clear }; + +struct Pipelines +{ + osp::PipelineDef vec; +}; + +} // namespace test_a + +// Test pipeline consisting of parallel tasks +TEST(Tasks, BasicSingleThreadedParallelTasks) +{ + using namespace test_a; + using enum Stages; + + // NOTE + // If this was multithreaded, then multiple threads writing to a single container is a bad + // idea. The proper way to do this is to make a vector per-thread. Targets are still + // well-suited for this problem, as these per-thread vectors can all be represented with the + // same TargetId. + + using BasicTraits_t = BasicBuilderTraits&, int&)>; + using Builder_t = BasicTraits_t::Builder; + using TaskFuncVec_t = BasicTraits_t::FuncVec_t; + + constexpr int sc_repetitions = 32; + constexpr int sc_pusherTaskCount = 24; + constexpr int sc_totalTaskCount = sc_pusherTaskCount + 2; + std::mt19937 randGen(69); + + // Step 1: Create tasks + + Tasks tasks; + TaskEdges edges; + TaskFuncVec_t functions; + Builder_t builder{tasks, edges, functions}; + auto pl = builder.create_pipelines(); + + // Multiple tasks push to the vector + for (int i = 0; i < sc_pusherTaskCount; ++i) + { + builder.task() + .run_on (pl.vec(Fill)) + .func( [] (int const in, std::vector& rOut, int &rChecksRun) -> TaskActions + { + rOut.push_back(in); + return {}; + }); + } + + // Use vector + builder.task() + .run_on(pl.vec(Use)) + .func( [] (int const in, std::vector& rOut, int &rChecksRun) -> TaskActions + { + int const sum = std::accumulate(rOut.begin(), rOut.end(), 0); + EXPECT_EQ(sum, in * sc_pusherTaskCount); + ++rChecksRun; + return {}; + }); + + // Clear vector after use + builder.task() + .run_on({pl.vec(Clear)}) + .func( [] (int const in, std::vector& rOut, int &rChecksRun) -> TaskActions + { + rOut.clear(); + return {}; + }); + + // Step 2: Compile tasks into an execution graph + + TaskGraph const graph = make_exec_graph(tasks, {&edges}); + + // Step 3: Run + + ExecContext exec; + exec_conform(tasks, exec); + + int checksRun = 0; + int input = 0; + std::vector output; + + // Repeat with randomness to test many possible execution orders + for (int i = 0; i < sc_repetitions; ++i) + { + input = 1 + int(randGen() % 30); + + exec_request_run(exec, pl.vec); + exec_update(tasks, graph, exec); + + randomized_singlethreaded_execute(tasks, graph, exec, randGen, sc_totalTaskCount, [&functions, &input, &output, &checksRun] (TaskId const task) -> TaskActions + { + return functions[task](input, output, checksRun); + }); + } + + ASSERT_EQ(checksRun, sc_repetitions); +} + +//----------------------------------------------------------------------------- + +namespace test_b +{ + +struct TestState +{ + int checks { 0 }; + bool normalDone { false }; + bool expectOptionalDone { false }; + bool optionalDone { false }; +}; + +enum class Stages { Schedule, Write, Read, Clear }; + +struct Pipelines +{ + osp::PipelineDef normal; + osp::PipelineDef optional; + + // Extra pipeline blocked by optional task to make the test case more difficult + osp::PipelineDef distraction; +}; + +} // namespace test_b + +// Test that features a 'normal' pipeline and an 'optional' pipeline that has a 50% chance of running +TEST(Tasks, BasicSingleThreadedOptional) +{ + using namespace test_b; + using enum Stages; + + using BasicTraits_t = BasicBuilderTraits; + using Builder_t = BasicTraits_t::Builder; + using TaskFuncVec_t = BasicTraits_t::FuncVec_t; + + constexpr int sc_repetitions = 128; + std::mt19937 randGen(69); + + Tasks tasks; + TaskEdges edges; + TaskFuncVec_t functions; + Builder_t builder{tasks, edges, functions}; + + auto const pl = builder.create_pipelines(); + + builder.pipeline(pl.optional) + .parent(pl.normal); + + builder.pipeline(pl.distraction) + .parent(pl.normal); + + builder.task() + .run_on ({pl.optional(Schedule)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + if (rRand() % 2 == 0) + { + rState.expectOptionalDone = true; + return { }; + } + else + { + return TaskAction::Cancel; + } + }); + + builder.task() + .run_on ({pl.normal(Write)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + rState.normalDone = true; + return {}; + }); + + builder.task() + .run_on ({pl.optional(Write)}) + .sync_with({pl.distraction(Read)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + rState.optionalDone = true; + return {}; + }); + + builder.task() + .run_on ({pl.normal(Read)}) + .sync_with({pl.optional(Read)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + ++ rState.checks; + EXPECT_TRUE(rState.normalDone); + EXPECT_EQ(rState.expectOptionalDone, rState.optionalDone); + return {}; + }); + + builder.task() + .run_on ({pl.normal(Clear)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + rState.normalDone = false; + rState.expectOptionalDone = false; + rState.optionalDone = false; + return {}; + }); + + builder.task() + .run_on ({pl.distraction(Write)}) + .func( [] (TestState&, std::mt19937&) -> TaskActions + { + return {}; + }); + + builder.task() + .run_on ({pl.distraction(Read)}) + .func( [] (TestState&, std::mt19937&) -> TaskActions + { + return {}; + }); + + + TaskGraph const graph = make_exec_graph(tasks, {&edges}); + + // Execute + + ExecContext exec; + exec_conform(tasks, exec); + + TestState world; + + for (int i = 0; i < sc_repetitions; ++i) + { + exec_request_run(exec, pl.normal); + exec_update(tasks, graph, exec); + + randomized_singlethreaded_execute( + tasks, graph, exec, randGen, 10, + [&functions, &world, &randGen] (TaskId const task) -> TaskActions + { + return functions[task](world, randGen); + }); + } + + // Assure that the tasks above actually ran, and didn't just skip everything + // Max of 5 tasks run each loop + ASSERT_GT(world.checks, sc_repetitions / 5); +} + +//----------------------------------------------------------------------------- + +namespace test_c +{ + +struct TestState +{ + std::vector inputQueue; + std::vector outputQueue; + int intermediate { 0 }; + + int checks { 0 }; + int outSumExpected { 0 }; +}; + +enum class Stages { Schedule, Process, Done, Clear }; + +struct Pipelines +{ + osp::PipelineDef main; + osp::PipelineDef loop; + osp::PipelineDef stepA; + osp::PipelineDef stepB; +}; + +} // namespace test_c + +// Looping pipelines with 2 child pipelines that run a 2-step process +TEST(Tasks, BasicSingleThreadedLoop) +{ + using namespace test_c; + using enum Stages; + + using BasicTraits_t = BasicBuilderTraits; + using Builder_t = BasicTraits_t::Builder; + using TaskFuncVec_t = BasicTraits_t::FuncVec_t; + + constexpr int sc_repetitions = 42; + std::mt19937 randGen(69); + + Tasks tasks; + TaskEdges edges; + TaskFuncVec_t functions; + Builder_t builder{tasks, edges, functions}; + + auto const pl = builder.create_pipelines(); + + builder.pipeline(pl.loop) .parent(pl.main).loops(true); + builder.pipeline(pl.stepA).parent(pl.loop); + builder.pipeline(pl.stepB).parent(pl.loop); + + // Determine if we should loop or not + builder.task() + .run_on ({pl.loop(Schedule)}) + .sync_with({pl.main(Process), pl.stepA(Schedule), pl.stepB(Schedule)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + if (rState.inputQueue.empty()) + { + return TaskAction::Cancel; + } + + return { }; + }); + + // Consume one item from input queue and writes to intermediate value + builder.task() + .run_on ({pl.stepA(Process)}) + .sync_with({pl.main(Process), pl.loop(Process)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + rState.intermediate = rState.inputQueue.back() * 2; + rState.inputQueue.pop_back(); + return { }; + }); + + // Read intermediate value and write to output queue + builder.task() + .run_on ({pl.stepB(Process)}) + .sync_with({pl.main(Process), pl.stepA(Done), pl.loop(Process)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + rState.outputQueue.push_back(rState.intermediate + 5); + return { }; + }); + + // Verify output queue is correct + builder.task() + .run_on ({pl.main(Done)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + ++ rState.checks; + int const sum = std::reduce(rState.outputQueue.begin(), rState.outputQueue.end()); + EXPECT_TRUE(rState.outSumExpected == sum); + return { }; + }); + + // Clear output queue after use + builder.task() + .run_on ({pl.main(Clear)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + rState.outputQueue.clear(); + return { }; + }); + + TaskGraph const graph = make_exec_graph(tasks, {&edges}); + + // Execute + + ExecContext exec; + exec_conform(tasks, exec); + + TestState world; + + world.inputQueue.reserve(64); + world.outputQueue.reserve(64); + + for (int i = 0; i < sc_repetitions; ++i) + { + int outSumExpected = 0; + world.inputQueue.resize(randGen() % 64); + for (int &rNum : world.inputQueue) + { + rNum = int(randGen() % 64); + outSumExpected += rNum * 2 + 5; + } + world.outSumExpected = outSumExpected; + + exec_request_run(exec, pl.main); + exec_update(tasks, graph, exec); + + randomized_singlethreaded_execute( + tasks, graph, exec, randGen, 999999, + [&functions, &world, &randGen] (TaskId const task) -> TaskActions + { + return functions[task](world, randGen); + }); + } + + ASSERT_EQ(world.checks, sc_repetitions); +} + +//----------------------------------------------------------------------------- + +namespace test_d +{ + +struct TestState +{ + int countIn { 0 }; + + int countOut { 0 }; + int countOutExpected { 0 }; + int outerLoops { 0 }; + + int checks { 0 }; +}; + +enum class Stages { Signal, Schedule, Process, Done, Clear }; + +struct Pipelines +{ + osp::PipelineDef loopOuter; + osp::PipelineDef loopInner; + osp::PipelineDef aux; +}; + +} // namespace test_d + +// Looping 'outer' pipeline with a nested looping 'inner' pipeline +TEST(Tasks, BasicSingleThreadedNestedLoop) +{ + using namespace test_d; + using enum Stages; + + using BasicTraits_t = BasicBuilderTraits; + using Builder_t = BasicTraits_t::Builder; + using TaskFuncVec_t = BasicTraits_t::FuncVec_t; + + constexpr int sc_repetitions = 42; + std::mt19937 randGen(69); + + Tasks tasks; + TaskEdges edges; + TaskFuncVec_t functions; + Builder_t builder{tasks, edges, functions}; + + auto const pl = builder.create_pipelines(); + + builder.pipeline(pl.loopOuter).loops(true).wait_for_signal(Signal); + builder.pipeline(pl.loopInner).loops(true).parent(pl.loopOuter); + + builder.task() + .run_on ({pl.loopInner(Schedule)}) + .sync_with({pl.loopOuter(Process)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + if (rState.countIn == 0) + { + return TaskAction::Cancel; + } + + return { }; + }); + + builder.task() + .run_on ({pl.loopInner(Process)}) + .sync_with({pl.loopOuter(Process)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + -- rState.countIn; + ++ rState.countOut; + return { }; + }); + + builder.task() + .run_on ({pl.loopOuter(Done)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + ++ rState.checks; + EXPECT_EQ(rState.countOut, rState.countOutExpected); + return { }; + }); + + builder.task() + .run_on ({pl.loopOuter(Clear)}) + .func( [] (TestState& rState, std::mt19937 &rRand) -> TaskActions + { + rState.countOut = 0; + return { }; + }); + + + TaskGraph const graph = make_exec_graph(tasks, {&edges}); + + // Execute + + ExecContext exec; + exec_conform(tasks, exec); + + TestState world; + + exec_request_run(exec, pl.loopOuter); + + for (int i = 0; i < sc_repetitions; ++i) + { + auto const count = int(randGen() % 10); + + world.countIn = count; + world.countOutExpected = count; + + exec_update(tasks, graph, exec); + + exec_signal(exec, pl.loopOuter); + + randomized_singlethreaded_execute( + tasks, graph, exec, randGen, 50, + [&functions, &world, &randGen] (TaskId const task) -> TaskActions + { + return functions[task](world, randGen); + }); + } + + ASSERT_EQ(world.checks, sc_repetitions); +} + +//----------------------------------------------------------------------------- + +namespace test_gameworld +{ + struct World { - int m_deltaTimeIn{0}; + int m_deltaTimeIn{1}; int m_forces{0}; int m_positions{0}; std::set m_canvas; }; -// Single-threaded test against World with order-dependent tasks -TEST(Tasks, SingleThreaded) +enum class StgSimple { Recalc, Use }; +enum class StgRender { Render, Done }; + +struct Pipelines { - constexpr uint32_t const sc_seed = 69; - constexpr int const sc_repetitions = 32; + osp::PipelineDef time; /// External time input, manually set dirty when time 'changes', and the world needs to update + osp::PipelineDef forces; /// Forces need to be calculated before physics + osp::PipelineDef positions; /// Positions calculated by physics task + osp::PipelineDef render; /// External render request, manually set dirty when a new frame to render is required +}; - // Setup structs for storing tasks and tags +} // namespace test_gameworld - Tags tags; - Tasks tasks; - tags.m_tags.reserve(128); // Max 128 tags, aka: just two 64-bit integers - tags.m_tagDepends.resize(tags.m_tags.capacity() * tags.m_tagDependsPerTag, - lgrn::id_null()); - tags.m_tagLimits.resize(tags.m_tags.capacity()); - tags.m_tagExtern.resize(tags.m_tags.capacity()); - TaskDataVec> functions; +// Single-threaded test against World with order-dependent tasks +TEST(Tasks, BasicSingleThreadedGameWorld) +{ + using namespace test_gameworld; + using enum StgSimple; + using enum StgRender; - // Create tags and set relationships between them + using BasicTraits_t = BasicBuilderTraits; + using Builder_t = BasicTraits_t::Builder; + using TaskFuncVec_t = BasicTraits_t::FuncVec_t; - auto builder = TaskBuilder{tags, tasks, functions}; + constexpr int sc_repetitions = 128; + std::mt19937 randGen(69); - // Tags are simply enum class integers - auto const [updWorld, updRender, forces, physics, needPhysics, draw] - = builder.create_tags<6>(); + Tasks tasks; + TaskEdges edges; + TaskFuncVec_t functions; + Builder_t builder{tasks, edges, functions}; - // Limit sets how many tasks using a certain tag can run simultaneously, but - // are unused in this test. - // Dependency tags restricts a task from running until all tasks containing - // tags it depends on are compelete. - builder.tag(forces).limit(1); - builder.tag(physics).depend_on({forces}); - builder.tag(needPhysics).depend_on({physics}); - builder.tag(draw).limit(1); + auto const pl = builder.create_pipelines(); - // Start adding tasks / systems. The order these are added does not matter. + // Start adding tasks. The order these are added does not matter. - // Calculate forces needed by the physics update + // Two tasks calculate forces needed by the physics update builder.task() - .assign({updWorld, forces}) - .data([] (World& rWorld) + .run_on ({pl.time(Use)}) + .sync_with({pl.forces(Recalc)}) + .func( [] (World& rWorld) -> TaskActions { rWorld.m_forces += 42 * rWorld.m_deltaTimeIn; + return {}; }); builder.task() - .assign({updWorld, forces}) - .data([] (World& rWorld) + .run_on ({pl.time(Use)}) + .sync_with({pl.forces(Recalc)}) + .func([] (World& rWorld) -> TaskActions { rWorld.m_forces += 1337 * rWorld.m_deltaTimeIn; + return {}; }); // Main Physics update builder.task() - .assign({updWorld, physics}) - .data([] (World& rWorld) + .run_on ({pl.time(Use)}) + .sync_with({pl.forces(Use), pl.positions(Recalc)}) + .func([] (World& rWorld) -> TaskActions { EXPECT_EQ(rWorld.m_forces, 1337 + 42); rWorld.m_positions += rWorld.m_forces; rWorld.m_forces = 0; - }); - // Physics update can be split into many smaller tasks. Tasks tagged with - // 'needPhysics' will run once ALL tasks tagged with 'physics' are done. - builder.task() - .assign({updWorld, physics}) - .data([] (World& rWorld) - { - rWorld.m_deltaTimeIn = 0; + return {}; }); // Draw things moved by physics update. If 'updWorld' wasn't enqueued, then // this will still run, as no 'needPhysics' tasks are incomplete builder.task() - .assign({updRender, needPhysics, draw}) - .data([] (World& rWorld) + .run_on ({pl.render(Render)}) + .sync_with({pl.positions(Use)}) + .func([] (World& rWorld) -> TaskActions { EXPECT_EQ(rWorld.m_positions, 1337 + 42); rWorld.m_canvas.emplace("Physics Cube"); + return {}; }); // Draw things unrelated to physics. This is allowed to be the first task // to run builder.task() - .assign({updRender, draw}) - .data([] (World& rWorld) + .run_on ({pl.render(Render)}) + .func([] (World& rWorld) -> TaskActions { rWorld.m_canvas.emplace("Terrain"); + return {}; }); - // Start execution - - ExecutionContext exec; - exec.m_tagIncompleteCounts .resize(tags.m_tags.capacity(), 0); - exec.m_tagRunningCounts .resize(tags.m_tags.capacity(), 0); - exec.m_taskQueuedCounts .resize(tasks.m_tasks.capacity(), 0); + TaskGraph const graph = make_exec_graph(tasks, {&edges}); - std::mt19937 gen(sc_seed); + // Execute - std::vector tagsToRun(tags.m_tags.vec().size()); - to_bitspan({updWorld, updRender}, tagsToRun); + ExecContext exec; + exec_conform(tasks, exec); World world; // Repeat (with randomness) to test many possible execution orders for (int i = 0; i < sc_repetitions; ++i) { - // Enqueue all tasks tagged with updWorld and updRender - task_enqueue(tags, tasks, exec, tagsToRun); - - // Its best to pass all external information (such as delta time) into - // the systems instead of leaving them to figure it out. - // Enqueuing should be similar to setting a dirty flag. world.m_deltaTimeIn = 1; world.m_positions = 0; world.m_canvas.clear(); - // Run until there's no tasks left to run - while (true) + // Enqueue initial tasks + // This roughly indicates "Time has changed" and "Render requested" + exec_request_run(exec, pl.time); + exec_request_run(exec, pl.forces); + exec_request_run(exec, pl.positions); + exec_request_run(exec, pl.render); + exec_update(tasks, graph, exec); + + randomized_singlethreaded_execute( + tasks, graph, exec, randGen, 5, + [&functions, &world] (TaskId const task) -> TaskActions { - std::vector tasksToRun(tasks.m_tasks.vec().size()); - task_list_available(tags, tasks, exec, tasksToRun); - auto const tasksToRunBits = lgrn::bit_view(tasksToRun); - unsigned int const availableCount = tasksToRunBits.count(); - - if (availableCount == 0) - { - break; - } - - // Choose a random available task - auto const choice = std::uniform_int_distribution{0, availableCount - 1}(gen); - auto const task = TaskId(*std::next(tasksToRunBits.ones().begin(), choice)); - - task_start(tags, tasks, exec, task); - functions.m_taskData[std::size_t(task)](world); - task_finish(tags, tasks, exec, task); - } + return functions[task](world); + }); ASSERT_TRUE(world.m_canvas.contains("Physics Cube")); ASSERT_TRUE(world.m_canvas.contains("Terrain"));