Index: binaries/data/config/default.cfg =================================================================== --- binaries/data/config/default.cfg +++ binaries/data/config/default.cfg @@ -71,7 +71,7 @@ ; if false, actors won't be rendered but anything entity will be. renderactors = true - +maxcorpsecount = -1 ; When enabled, limits the number of corpses to the number specified. watereffects=true ; When disabled, force usage of the fixed pipeline water. This is faster, but really, really ugly. waterfancyeffects = false waterrealdepth = true Index: binaries/data/mods/public/gui/options/options.json =================================================================== --- binaries/data/mods/public/gui/options/options.json +++ binaries/data/mods/public/gui/options/options.json @@ -257,6 +257,14 @@ "config": "adaptivefps.session", "min": 20, "max": 100 + }, + { + "type": "number", + "label": "Max number of corpses", + "tooltip": "To save CPU workload, limits the number of corpses to display. -1 is unlimited.", + "config": "maxcorpsecount", + "min": -1, + "max": 600 } ] }, Index: binaries/data/mods/public/simulation/components/Health.js =================================================================== --- binaries/data/mods/public/simulation/components/Health.js +++ binaries/data/mods/public/simulation/components/Health.js @@ -352,7 +352,14 @@ if (resource) entCorpse = Engine.AddEntity("resource|" + templateName); else + { + let cmpUnitRenderer = Engine.QueryInterface(SYSTEM_ENTITY, IID_UnitRenderer); + if (!cmpUnitRenderer || !cmpUnitRenderer.ShouldCreateCorpse()) + return; + + warn("trace"); entCorpse = Engine.AddLocalEntity("corpse|" + templateName); + } // Copy various parameters so it looks just like us. let cmpPositionCorpse = Engine.QueryInterface(entCorpse, IID_Position); Index: binaries/data/mods/public/simulation/components/tests/test_Health.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Health.js +++ binaries/data/mods/public/simulation/components/tests/test_Health.js @@ -20,7 +20,6 @@ "Unhealable": false }; - var injured_flag = false; var corpse_entity; @@ -77,7 +76,6 @@ TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50); TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false); TS_ASSERT_EQUALS(cmpHealth.IsUnhealable(), true); - // Check death. Engine.AddLocalEntity = function(template) { corpse_entity = template; @@ -97,10 +95,22 @@ return corpse_id; }; + +let numberOfCorpses = 0; + +AddMock(SYSTEM_ENTITY, IID_UnitRenderer, { + "ShouldCreateCorpse": () => numberOfCorpses < 1 +}); + +let querySpy = new Spy(Engine, "AddLocalEntity"); change = cmpHealth.Reduce(50); + +// Fake the engine addition of the new corpse. +++numberOfCorpses; // Assert we create a corpse with the proper template. TS_ASSERT_EQUALS(corpse_entity, "corpse|test"); +TS_ASSERT_EQUALS(querySpy._called, 1); // Check that we are not marked as injured. TS_ASSERT_EQUALS(injured_flag, false); @@ -144,3 +154,11 @@ TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50); TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false); TS_ASSERT_EQUALS(cmpHealth.IsUnhealable(), true); + +// Test that a new local entity is not created if the config does not allow it. +querySpy._reset(); +cmpHealth = setEntityUp(); +// Kill the entity +change = cmpHealth.Reduce(80); +// It was only called the first time. +TS_ASSERT_EQUALS(querySpy._called, 0); Index: source/renderer/RenderingOptions.h =================================================================== --- source/renderer/RenderingOptions.h +++ source/renderer/RenderingOptions.h @@ -105,6 +105,7 @@ OPTION(DisplayFrustum, bool); OPTION(DisplayShadowsFrustum, bool); + OPTION(MaxCorpseCount, int); OPTION(RenderActors, bool); #undef OPTION_DEFAULT_SETTER Index: source/renderer/RenderingOptions.cpp =================================================================== --- source/renderer/RenderingOptions.cpp +++ source/renderer/RenderingOptions.cpp @@ -98,6 +98,7 @@ m_DisplayFrustum = false; m_DisplayShadowsFrustum = false; m_RenderActors = true; + m_MaxCorpseCount = -1; } CRenderingOptions::~CRenderingOptions() @@ -180,6 +181,7 @@ m_GPUSkinning = true; }); + m_ConfigHooks->Setup("maxcorpsecount", m_MaxCorpseCount); m_ConfigHooks->Setup("renderactors", m_RenderActors); } Index: source/simulation2/components/CCmpDecay.cpp =================================================================== --- source/simulation2/components/CCmpDecay.cpp +++ source/simulation2/components/CCmpDecay.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include "ICmpPosition.h" #include "ICmpTerrain.h" +#include "ICmpUnitRenderer.h" #include "ICmpVisual.h" #include "ps/Profile.h" @@ -107,8 +108,17 @@ m_Active = false; } - if (m_Active) + if (!m_Active) + return; + + CmpPtr cmpUnitRenderer(GetSystemEntity()); + if (cmpUnitRenderer && cmpUnitRenderer->ShouldCreateCorpse()) + { GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_Interpolate, this, true); + cmpUnitRenderer->RegisterCorpse(GetEntityId()); + } + else + m_Active = false; } virtual void Deinit() Index: source/simulation2/components/CCmpUnitRenderer.cpp =================================================================== --- source/simulation2/components/CCmpUnitRenderer.cpp +++ source/simulation2/components/CCmpUnitRenderer.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -35,6 +35,7 @@ #include "graphics/Unit.h" #include "maths/BoundingSphere.h" #include "maths/Matrix3D.h" +#include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Profile.h" #include "renderer/RenderingOptions.h" @@ -42,6 +43,9 @@ #include "tools/atlas/GameInterface/GameLoop.h" +#include +#include + /** * Efficiently(ish) renders all the units in the world. * @@ -117,6 +121,10 @@ std::vector m_Units; std::vector m_UnitTagsFree; + /** + * Contains the list of corpses currently displayed. + */ + std::deque m_Corpses; int m_FrameNumber; float m_FrameOffset; @@ -189,6 +197,28 @@ return &m_Units[tag.n - 1]; } + virtual void RegisterCorpse(entity_id_t entity) + { + const int maxCorpses = g_RenderingOptions.GetMaxCorpseCount(); + if (maxCorpses <= 0) + return; + + if (m_Corpses.size() == static_cast(maxCorpses)) + { + const entity_id_t ent = m_Corpses.front(); + // This will work even if the entity has been destroyed already by CCmpDecay. + GetSimContext().GetComponentManager().DestroyComponentsSoon(ent); + m_Corpses.pop_front(); + } + + m_Corpses.push_back(entity); + } + + virtual bool ShouldCreateCorpse() const + { + return g_RenderingOptions.GetMaxCorpseCount() != 0; + } + virtual tag_t AddUnit(CEntityHandle entity, CUnit* actor, const CBoundingSphere& boundsApprox, int flags) { ENSURE(actor != NULL); Index: source/simulation2/components/ICmpUnitRenderer.h =================================================================== --- source/simulation2/components/ICmpUnitRenderer.h +++ source/simulation2/components/ICmpUnitRenderer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -52,6 +52,10 @@ virtual void RemoveUnit(tag_t tag) = 0; + virtual void RegisterCorpse(entity_id_t entity) = 0; + + virtual bool ShouldCreateCorpse() const = 0; + virtual void UpdateUnit(tag_t tag, CUnit* unit, const CBoundingSphere& boundsApprox) = 0; virtual void UpdateUnitPos(tag_t tag, bool inWorld, const CVector3D& pos0, const CVector3D& pos1) = 0; Index: source/simulation2/components/ICmpUnitRenderer.cpp =================================================================== --- source/simulation2/components/ICmpUnitRenderer.cpp +++ source/simulation2/components/ICmpUnitRenderer.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -23,4 +23,5 @@ BEGIN_INTERFACE_WRAPPER(UnitRenderer) DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpUnitRenderer, SetDebugOverlay, bool) +DEFINE_INTERFACE_METHOD_CONST_0("ShouldCreateCorpse", bool, ICmpUnitRenderer, ShouldCreateCorpse) END_INTERFACE_WRAPPER(UnitRenderer)