refactor(srp): remove managed frame planning hook
- derive fullscreen stage planning from pipeline render-graph support - trim planning-only APIs from the managed SRP bridge and public surface - update probes and tests to lock the slimmer SRP API boundary
This commit is contained in:
@@ -45,7 +45,10 @@ void RenderPipelineHost::SetPipelineAsset(std::shared_ptr<const RenderPipelineAs
|
||||
std::vector<CameraFramePlan> RenderPipelineHost::BuildFramePlans(
|
||||
const std::vector<CameraRenderRequest>& requests) {
|
||||
return m_framePlanBuilder != nullptr
|
||||
? m_framePlanBuilder->BuildPlans(requests, GetPipelineAsset())
|
||||
? m_framePlanBuilder->BuildPlans(
|
||||
requests,
|
||||
GetPipelineAsset(),
|
||||
GetPipeline())
|
||||
: std::vector<CameraFramePlan>();
|
||||
}
|
||||
|
||||
|
||||
@@ -89,17 +89,6 @@ FinalColorSettings ManagedScriptableRenderPipelineAsset::GetDefaultFinalColorSet
|
||||
return m_executionHostAsset.GetDefaultFinalColorSettings();
|
||||
}
|
||||
|
||||
void ManagedScriptableRenderPipelineAsset::ConfigureCameraFramePlan(
|
||||
CameraFramePlan& plan) const {
|
||||
RenderPipelineAsset::ConfigureCameraFramePlan(plan);
|
||||
|
||||
if (const std::shared_ptr<const ManagedRenderPipelineAssetRuntime> runtime =
|
||||
ResolveManagedAssetRuntime();
|
||||
runtime != nullptr) {
|
||||
runtime->ConfigureCameraFramePlan(plan);
|
||||
}
|
||||
}
|
||||
|
||||
void SetManagedRenderPipelineBridge(
|
||||
std::shared_ptr<const ManagedRenderPipelineBridge> bridge) {
|
||||
GetGraphicsSettingsState().SetManagedRenderPipelineBridge(
|
||||
|
||||
@@ -1,15 +1,76 @@
|
||||
#include "Rendering/Planning/CameraFramePlanBuilder.h"
|
||||
|
||||
#include "Rendering/RenderPipeline.h"
|
||||
#include "Rendering/RenderPipelineAsset.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
namespace {
|
||||
|
||||
bool UsesExplicitFullscreenSource(
|
||||
const FullscreenPassRenderRequest& request,
|
||||
CameraFrameColorSource source) {
|
||||
return source == CameraFrameColorSource::ExplicitSurface &&
|
||||
HasValidColorTarget(request.sourceSurface);
|
||||
}
|
||||
|
||||
void ConfigureFullscreenStagesFromPipeline(
|
||||
CameraFramePlan& plan,
|
||||
const RenderPipeline* pipeline) {
|
||||
const bool supportsPostProcess =
|
||||
pipeline != nullptr &&
|
||||
pipeline->SupportsStageRenderGraph(
|
||||
CameraFrameStage::PostProcess);
|
||||
const bool supportsFinalOutput =
|
||||
pipeline != nullptr &&
|
||||
pipeline->SupportsStageRenderGraph(
|
||||
CameraFrameStage::FinalOutput);
|
||||
const bool hasPostProcess =
|
||||
plan.postProcess.IsRequested() ||
|
||||
supportsPostProcess;
|
||||
const bool hasFinalOutput =
|
||||
plan.finalOutput.IsRequested() ||
|
||||
supportsFinalOutput;
|
||||
|
||||
if (hasPostProcess &&
|
||||
!UsesExplicitFullscreenSource(
|
||||
plan.postProcess,
|
||||
plan.ResolveStageColorSource(
|
||||
CameraFrameStage::PostProcess))) {
|
||||
plan.RequestFullscreenStage(
|
||||
CameraFrameStage::PostProcess,
|
||||
CameraFrameColorSource::MainSceneColor,
|
||||
hasFinalOutput);
|
||||
}
|
||||
|
||||
const bool canChainFromPostProcess =
|
||||
hasPostProcess &&
|
||||
!UsesExplicitFullscreenSource(
|
||||
plan.postProcess,
|
||||
plan.ResolveStageColorSource(
|
||||
CameraFrameStage::PostProcess));
|
||||
if (hasFinalOutput &&
|
||||
!UsesExplicitFullscreenSource(
|
||||
plan.finalOutput,
|
||||
plan.ResolveStageColorSource(
|
||||
CameraFrameStage::FinalOutput))) {
|
||||
plan.RequestFullscreenStage(
|
||||
CameraFrameStage::FinalOutput,
|
||||
canChainFromPostProcess
|
||||
? CameraFrameColorSource::PostProcessColor
|
||||
: CameraFrameColorSource::MainSceneColor);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<CameraFramePlan> CameraFramePlanBuilder::BuildPlans(
|
||||
const std::vector<CameraRenderRequest>& requests,
|
||||
const RenderPipelineAsset* pipelineAsset) {
|
||||
const RenderPipelineAsset* pipelineAsset,
|
||||
const RenderPipeline* pipeline) {
|
||||
std::vector<CameraFramePlan> plans = CreatePlansFromRequests(requests);
|
||||
ConfigurePlans(plans, pipelineAsset);
|
||||
ConfigurePlans(plans, pipelineAsset, pipeline);
|
||||
return plans;
|
||||
}
|
||||
|
||||
@@ -26,13 +87,18 @@ std::vector<CameraFramePlan> CameraFramePlanBuilder::CreatePlansFromRequests(
|
||||
|
||||
void CameraFramePlanBuilder::ConfigurePlans(
|
||||
std::vector<CameraFramePlan>& plans,
|
||||
const RenderPipelineAsset* pipelineAsset) const {
|
||||
const RenderPipelineAsset* pipelineAsset,
|
||||
const RenderPipeline* pipeline) const {
|
||||
for (CameraFramePlan& plan : plans) {
|
||||
if (pipelineAsset != nullptr) {
|
||||
pipelineAsset->ConfigureCameraFramePlan(plan);
|
||||
} else {
|
||||
ApplyDefaultRenderPipelineAssetCameraFramePlanPolicy(plan);
|
||||
}
|
||||
|
||||
ConfigureFullscreenStagesFromPipeline(
|
||||
plan,
|
||||
pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
class RenderPipelineAsset;
|
||||
class RenderPipeline;
|
||||
|
||||
class CameraFramePlanBuilder {
|
||||
public:
|
||||
@@ -18,14 +19,16 @@ public:
|
||||
|
||||
std::vector<CameraFramePlan> BuildPlans(
|
||||
const std::vector<CameraRenderRequest>& requests,
|
||||
const RenderPipelineAsset* pipelineAsset);
|
||||
const RenderPipelineAsset* pipelineAsset,
|
||||
const RenderPipeline* pipeline);
|
||||
|
||||
private:
|
||||
std::vector<CameraFramePlan> CreatePlansFromRequests(
|
||||
const std::vector<CameraRenderRequest>& requests) const;
|
||||
void ConfigurePlans(
|
||||
std::vector<CameraFramePlan>& plans,
|
||||
const RenderPipelineAsset* pipelineAsset) const;
|
||||
const RenderPipelineAsset* pipelineAsset,
|
||||
const RenderPipeline* pipeline) const;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
@@ -111,11 +111,6 @@ struct ManagedScriptableRenderPipelineCameraRequestContextState {
|
||||
size_t renderedRequestCount = 0u;
|
||||
};
|
||||
|
||||
struct ManagedScriptableRenderPipelinePlanningContextState {
|
||||
uint64_t handle = 0;
|
||||
Rendering::CameraFramePlan* plan = nullptr;
|
||||
};
|
||||
|
||||
uint64_t& GetManagedScriptableRenderContextNextHandle() {
|
||||
static uint64_t nextHandle = 1;
|
||||
return nextHandle;
|
||||
@@ -279,49 +274,6 @@ void UnregisterManagedScriptableRenderPipelineCameraRequestContextState(
|
||||
GetManagedScriptableRenderPipelineCameraRequestContextRegistry().erase(handle);
|
||||
}
|
||||
|
||||
uint64_t& GetManagedScriptableRenderPipelinePlanningContextNextHandle() {
|
||||
static uint64_t nextHandle = 1;
|
||||
return nextHandle;
|
||||
}
|
||||
|
||||
std::unordered_map<uint64_t, ManagedScriptableRenderPipelinePlanningContextState*>&
|
||||
GetManagedScriptableRenderPipelinePlanningContextRegistry() {
|
||||
static std::unordered_map<uint64_t, ManagedScriptableRenderPipelinePlanningContextState*>
|
||||
registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
ManagedScriptableRenderPipelinePlanningContextState*
|
||||
FindManagedScriptableRenderPipelinePlanningContextState(
|
||||
uint64_t handle) {
|
||||
const auto it =
|
||||
GetManagedScriptableRenderPipelinePlanningContextRegistry().find(handle);
|
||||
return it != GetManagedScriptableRenderPipelinePlanningContextRegistry().end()
|
||||
? it->second
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
uint64_t RegisterManagedScriptableRenderPipelinePlanningContextState(
|
||||
ManagedScriptableRenderPipelinePlanningContextState& state) {
|
||||
uint64_t handle =
|
||||
GetManagedScriptableRenderPipelinePlanningContextNextHandle()++;
|
||||
if (handle == 0) {
|
||||
handle = GetManagedScriptableRenderPipelinePlanningContextNextHandle()++;
|
||||
}
|
||||
|
||||
state.handle = handle;
|
||||
GetManagedScriptableRenderPipelinePlanningContextRegistry()[handle] = &state;
|
||||
return handle;
|
||||
}
|
||||
|
||||
void UnregisterManagedScriptableRenderPipelinePlanningContextState(uint64_t handle) {
|
||||
if (handle == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
GetManagedScriptableRenderPipelinePlanningContextRegistry().erase(handle);
|
||||
}
|
||||
|
||||
void CleanupMonoRootDomainAtExit() {
|
||||
MonoRootState& rootState = GetMonoRootState();
|
||||
if (!rootState.rootDomain) {
|
||||
@@ -764,9 +716,6 @@ public:
|
||||
const Rendering::DirectionalShadowPlanningSettings&
|
||||
directionalShadowSettings) const override;
|
||||
|
||||
void ConfigureCameraFramePlan(
|
||||
Rendering::CameraFramePlan& plan) const override;
|
||||
|
||||
bool TryGetDefaultFinalColorSettings(
|
||||
Rendering::FinalColorSettings& settings) const override;
|
||||
|
||||
@@ -789,8 +738,6 @@ private:
|
||||
MonoMethod* ResolveCreatePipelineMethod(MonoObject* assetObject) const;
|
||||
MonoMethod* ResolveConfigureCameraRenderRequestMethod(
|
||||
MonoObject* assetObject) const;
|
||||
MonoMethod* ResolveConfigureCameraFramePlanMethod(
|
||||
MonoObject* assetObject) const;
|
||||
MonoMethod* ResolveGetDefaultFinalColorSettingsMethod(
|
||||
MonoObject* assetObject) const;
|
||||
|
||||
@@ -800,7 +747,6 @@ private:
|
||||
mutable uint32_t m_assetHandle = 0;
|
||||
mutable MonoMethod* m_createPipelineMethod = nullptr;
|
||||
mutable MonoMethod* m_configureCameraRenderRequestMethod = nullptr;
|
||||
mutable MonoMethod* m_configureCameraFramePlanMethod = nullptr;
|
||||
mutable MonoMethod* m_getDefaultFinalColorSettingsMethod = nullptr;
|
||||
mutable bool m_ownsManagedAssetHandle = false;
|
||||
mutable bool m_assetCreationAttempted = false;
|
||||
@@ -1081,43 +1027,6 @@ void MonoManagedRenderPipelineAssetRuntime::ConfigureCameraRenderRequest(
|
||||
requestContextHandle);
|
||||
}
|
||||
|
||||
void MonoManagedRenderPipelineAssetRuntime::ConfigureCameraFramePlan(
|
||||
Rendering::CameraFramePlan& plan) const {
|
||||
if (!EnsureManagedAsset()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MonoObject* const assetObject = GetManagedAssetObject();
|
||||
MonoMethod* const method =
|
||||
ResolveConfigureCameraFramePlanMethod(assetObject);
|
||||
if (assetObject == nullptr || method == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ManagedScriptableRenderPipelinePlanningContextState planningContextState = {};
|
||||
planningContextState.plan = &plan;
|
||||
const uint64_t planningContextHandle =
|
||||
RegisterManagedScriptableRenderPipelinePlanningContextState(
|
||||
planningContextState);
|
||||
MonoObject* const planningContextObject =
|
||||
m_runtime->CreateManagedScriptableRenderPipelinePlanningContext(
|
||||
planningContextHandle);
|
||||
if (planningContextObject == nullptr) {
|
||||
UnregisterManagedScriptableRenderPipelinePlanningContextState(
|
||||
planningContextHandle);
|
||||
return;
|
||||
}
|
||||
|
||||
void* args[1] = { planningContextObject };
|
||||
m_runtime->InvokeManagedMethod(
|
||||
assetObject,
|
||||
method,
|
||||
args,
|
||||
nullptr);
|
||||
UnregisterManagedScriptableRenderPipelinePlanningContextState(
|
||||
planningContextHandle);
|
||||
}
|
||||
|
||||
bool MonoManagedRenderPipelineAssetRuntime::TryGetDefaultFinalColorSettings(
|
||||
Rendering::FinalColorSettings& settings) const {
|
||||
settings = {};
|
||||
@@ -1252,7 +1161,6 @@ bool MonoManagedRenderPipelineAssetRuntime::EnsureManagedAsset() const {
|
||||
void MonoManagedRenderPipelineAssetRuntime::ReleaseManagedAsset() const {
|
||||
m_createPipelineMethod = nullptr;
|
||||
m_configureCameraRenderRequestMethod = nullptr;
|
||||
m_configureCameraFramePlanMethod = nullptr;
|
||||
m_getDefaultFinalColorSettingsMethod = nullptr;
|
||||
const bool ownsManagedAssetHandle = m_ownsManagedAssetHandle;
|
||||
m_ownsManagedAssetHandle = false;
|
||||
@@ -1303,20 +1211,6 @@ MonoManagedRenderPipelineAssetRuntime::ResolveConfigureCameraRenderRequestMethod
|
||||
return m_configureCameraRenderRequestMethod;
|
||||
}
|
||||
|
||||
MonoMethod*
|
||||
MonoManagedRenderPipelineAssetRuntime::ResolveConfigureCameraFramePlanMethod(
|
||||
MonoObject* assetObject) const {
|
||||
if (m_configureCameraFramePlanMethod == nullptr) {
|
||||
m_configureCameraFramePlanMethod =
|
||||
m_runtime->ResolveManagedMethod(
|
||||
assetObject,
|
||||
"ConfigureCameraFramePlan",
|
||||
1);
|
||||
}
|
||||
|
||||
return m_configureCameraFramePlanMethod;
|
||||
}
|
||||
|
||||
MonoMethod*
|
||||
MonoManagedRenderPipelineAssetRuntime::ResolveGetDefaultFinalColorSettingsMethod(
|
||||
MonoObject* assetObject) const {
|
||||
@@ -3747,47 +3641,6 @@ void InternalCall_Rendering_ScriptableRenderPipelineCameraRequestContext_ClearDi
|
||||
state->request->directionalShadow = {};
|
||||
}
|
||||
|
||||
void InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage(
|
||||
uint64_t nativeHandle,
|
||||
int32_t stage) {
|
||||
ManagedScriptableRenderPipelinePlanningContextState* const state =
|
||||
FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle);
|
||||
const Rendering::CameraFrameStage frameStage =
|
||||
static_cast<Rendering::CameraFrameStage>(stage);
|
||||
if (state == nullptr ||
|
||||
state->plan == nullptr ||
|
||||
!Rendering::IsCameraFrameFullscreenSequenceStage(frameStage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
state->plan->ClearFullscreenStage(frameStage);
|
||||
}
|
||||
|
||||
mono_bool
|
||||
InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestFullscreenStage(
|
||||
uint64_t nativeHandle,
|
||||
int32_t stage,
|
||||
int32_t source,
|
||||
mono_bool usesGraphManagedOutputColor) {
|
||||
ManagedScriptableRenderPipelinePlanningContextState* const state =
|
||||
FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle);
|
||||
const Rendering::CameraFrameStage frameStage =
|
||||
static_cast<Rendering::CameraFrameStage>(stage);
|
||||
if (state == nullptr ||
|
||||
state->plan == nullptr ||
|
||||
!Rendering::IsCameraFrameFullscreenSequenceStage(frameStage) ||
|
||||
source == static_cast<int32_t>(Rendering::CameraFrameColorSource::ExplicitSurface)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return state->plan->RequestFullscreenStage(
|
||||
frameStage,
|
||||
static_cast<Rendering::CameraFrameColorSource>(source),
|
||||
usesGraphManagedOutputColor != 0)
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
void RegisterInternalCalls() {
|
||||
if (GetInternalCallRegistrationState()) {
|
||||
return;
|
||||
@@ -3975,8 +3828,6 @@ void RegisterInternalCalls() {
|
||||
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelineCameraRequestContext_GetRenderedRequestCount", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelineCameraRequestContext_GetRenderedRequestCount));
|
||||
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelineCameraRequestContext_GetHasDirectionalShadow", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelineCameraRequestContext_GetHasDirectionalShadow));
|
||||
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelineCameraRequestContext_ClearDirectionalShadow", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelineCameraRequestContext_ClearDirectionalShadow));
|
||||
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage));
|
||||
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_RequestFullscreenStage", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestFullscreenStage));
|
||||
|
||||
GetInternalCallRegistrationState() = true;
|
||||
}
|
||||
@@ -4036,8 +3887,6 @@ void MonoScriptRuntime::Shutdown() {
|
||||
GetManagedScriptableRenderContextNextHandle() = 1;
|
||||
GetManagedScriptableRenderPipelineCameraRequestContextRegistry().clear();
|
||||
GetManagedScriptableRenderPipelineCameraRequestContextNextHandle() = 1;
|
||||
GetManagedScriptableRenderPipelinePlanningContextRegistry().clear();
|
||||
GetManagedScriptableRenderPipelinePlanningContextNextHandle() = 1;
|
||||
ClearManagedInstances();
|
||||
ClearExternalManagedObjects();
|
||||
ClearClassCache();
|
||||
@@ -4055,12 +3904,10 @@ void MonoScriptRuntime::Shutdown() {
|
||||
m_scriptableRenderPipelineClass = nullptr;
|
||||
m_scriptableRenderContextClass = nullptr;
|
||||
m_scriptableRenderPipelineCameraRequestContextClass = nullptr;
|
||||
m_scriptableRenderPipelinePlanningContextClass = nullptr;
|
||||
m_serializeFieldAttributeClass = nullptr;
|
||||
m_gameObjectConstructor = nullptr;
|
||||
m_scriptableRenderContextConstructor = nullptr;
|
||||
m_scriptableRenderPipelineCameraRequestContextConstructor = nullptr;
|
||||
m_scriptableRenderPipelinePlanningContextConstructor = nullptr;
|
||||
m_managedGameObjectUUIDField = nullptr;
|
||||
m_gameObjectUUIDField = nullptr;
|
||||
m_scriptComponentUUIDField = nullptr;
|
||||
@@ -4854,27 +4701,6 @@ bool MonoScriptRuntime::DiscoverScriptClasses() {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_scriptableRenderPipelinePlanningContextClass = mono_class_from_name(
|
||||
m_coreImage,
|
||||
kManagedRenderingNamespace,
|
||||
"ScriptableRenderPipelinePlanningContext");
|
||||
if (!m_scriptableRenderPipelinePlanningContextClass) {
|
||||
SetError(
|
||||
"Failed to locate the managed ScriptableRenderPipelinePlanningContext type.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_scriptableRenderPipelinePlanningContextConstructor =
|
||||
mono_class_get_method_from_name(
|
||||
m_scriptableRenderPipelinePlanningContextClass,
|
||||
".ctor",
|
||||
1);
|
||||
if (!m_scriptableRenderPipelinePlanningContextConstructor) {
|
||||
SetError(
|
||||
"Failed to locate the managed ScriptableRenderPipelinePlanningContext constructor.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_serializeFieldAttributeClass = mono_class_from_name(
|
||||
m_coreImage,
|
||||
m_settings.baseNamespace.c_str(),
|
||||
@@ -5470,45 +5296,6 @@ MonoScriptRuntime::CreateManagedScriptableRenderPipelineCameraRequestContext(
|
||||
return contextObject;
|
||||
}
|
||||
|
||||
MonoObject* MonoScriptRuntime::CreateManagedScriptableRenderPipelinePlanningContext(
|
||||
uint64_t nativeHandle) {
|
||||
if (!m_initialized ||
|
||||
nativeHandle == 0 ||
|
||||
m_scriptableRenderPipelinePlanningContextClass == nullptr ||
|
||||
m_scriptableRenderPipelinePlanningContextConstructor == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
|
||||
MonoObject* const contextObject =
|
||||
mono_object_new(
|
||||
m_appDomain,
|
||||
m_scriptableRenderPipelinePlanningContextClass);
|
||||
if (contextObject == nullptr) {
|
||||
SetError(
|
||||
"Mono failed to allocate a managed ScriptableRenderPipelinePlanningContext.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* args[1];
|
||||
uint64_t nativeHandleArgument = nativeHandle;
|
||||
args[0] = &nativeHandleArgument;
|
||||
|
||||
MonoObject* exception = nullptr;
|
||||
mono_runtime_invoke(
|
||||
m_scriptableRenderPipelinePlanningContextConstructor,
|
||||
contextObject,
|
||||
args,
|
||||
&exception);
|
||||
if (exception != nullptr) {
|
||||
RecordException(exception);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return contextObject;
|
||||
}
|
||||
|
||||
MonoScriptRuntime::InstanceData* MonoScriptRuntime::FindInstance(const ScriptRuntimeContext& context) {
|
||||
const auto it = m_instances.find(InstanceKey{context.gameObjectUUID, context.scriptComponentUUID});
|
||||
return it != m_instances.end() ? &it->second : nullptr;
|
||||
|
||||
Reference in New Issue
Block a user