refactor(app): compose world rendering startup
Move Region/environment, mandatory modern rendering, terrain, WB, texture, and sampler construction behind the typed Phase-4 composition boundary. Give every fallible GL constructor prefix retryable ownership so partial startup failure cannot leak or replay resource deletion while preserving the accepted render path and DAT inputs. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
cd7b519f78
commit
6a2fe98cc4
22 changed files with 1983 additions and 634 deletions
534
src/AcDream.App/Composition/WorldRenderComposition.cs
Normal file
534
src/AcDream.App/Composition/WorldRenderComposition.cs
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
using System.Collections.Concurrent;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Content;
|
||||
using AcDream.Core.Terrain;
|
||||
using AcDream.UI.Abstractions.Settings;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Silk.NET.Input;
|
||||
using Silk.NET.OpenGL;
|
||||
using Shader = AcDream.App.Rendering.Shader;
|
||||
|
||||
namespace AcDream.App.Composition;
|
||||
|
||||
internal sealed record WorldRegionData(Region Region, float[] HeightTable);
|
||||
|
||||
internal sealed record WorldTerrainBuildContext(
|
||||
uint InitialCenterLandblockId,
|
||||
int InitialCenterX,
|
||||
int InitialCenterY,
|
||||
float[] HeightTable,
|
||||
TerrainBlendingContext Blending,
|
||||
ConcurrentDictionary<uint, SurfaceInfo> SurfaceCache);
|
||||
|
||||
internal sealed record WorldRenderFoundation(
|
||||
string ShadersDirectory,
|
||||
BindlessSupport Bindless,
|
||||
TerrainAtlas TerrainAtlas,
|
||||
Shader TerrainShader,
|
||||
SceneLightingUboBinding SceneLighting,
|
||||
DebugLineRenderer DebugLines,
|
||||
BitmapFont? DebugFont,
|
||||
TextRenderer? TextRenderer,
|
||||
TerrainModernRenderer Terrain,
|
||||
Shader MeshShader,
|
||||
WbMeshAdapter MeshAdapter,
|
||||
TextureCache TextureCache,
|
||||
SamplerCache Samplers);
|
||||
|
||||
internal sealed record WorldRenderResult(
|
||||
WorldTerrainBuildContext TerrainBuild,
|
||||
WorldRenderFoundation Foundation);
|
||||
|
||||
internal sealed record WorldRenderDependencies(
|
||||
WorldEnvironmentController Environment,
|
||||
IGameRenderResourceLifetime RenderResources,
|
||||
IGpuResourceRetirementQueue ResourceRetirement,
|
||||
uint InitialCenterLandblockId,
|
||||
Action<string> Log);
|
||||
|
||||
internal interface IGameWindowWorldRenderPublication
|
||||
{
|
||||
void PublishBindlessSupport(BindlessSupport value);
|
||||
void PublishTerrainShader(Shader value);
|
||||
void PublishSceneLighting(SceneLightingUboBinding value);
|
||||
void PublishDebugLines(DebugLineRenderer value);
|
||||
void PublishHudResources(BitmapFont font, TextRenderer text);
|
||||
void PublishTerrain(TerrainModernRenderer value);
|
||||
void PublishTerrainBuildState(
|
||||
float[] heightTable,
|
||||
TerrainBlendingContext blending,
|
||||
ConcurrentDictionary<uint, SurfaceInfo> surfaceCache);
|
||||
void PublishMeshShader(Shader value);
|
||||
void PublishWbMeshAdapter(WbMeshAdapter value);
|
||||
void PublishTextureCache(TextureCache value);
|
||||
void PublishSamplerCache(SamplerCache value);
|
||||
}
|
||||
|
||||
internal interface IWorldRenderCompositionFactory
|
||||
{
|
||||
void InitializeGlState(GL gl);
|
||||
WorldRegionData LoadRegion(IDatReaderWriter dats);
|
||||
void InitializeEnvironment(WorldEnvironmentController environment, Region region);
|
||||
BindlessSupport RequireBindless(GL gl, Action<string> log);
|
||||
TerrainAtlas AcquireTerrainAtlas(
|
||||
IGameRenderResourceLifetime lifetime,
|
||||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
BindlessSupport bindless);
|
||||
void SetTerrainAnisotropic(TerrainAtlas atlas, int level);
|
||||
Shader CreateTerrainShader(GL gl, string shadersDirectory);
|
||||
SceneLightingUboBinding CreateSceneLighting(GL gl);
|
||||
DebugLineRenderer CreateDebugLines(GL gl, string shadersDirectory);
|
||||
byte[]? TryLoadDebugFont();
|
||||
BitmapFont CreateDebugFont(GL gl, byte[] bytes);
|
||||
TextRenderer CreateTextRenderer(GL gl, string shadersDirectory);
|
||||
TerrainModernRenderer CreateTerrain(
|
||||
GL gl,
|
||||
BindlessSupport bindless,
|
||||
Shader shader,
|
||||
TerrainAtlas atlas,
|
||||
IGpuResourceRetirementQueue retirement);
|
||||
WorldTerrainBuildContext CreateTerrainBuildContext(
|
||||
uint initialCenterLandblockId,
|
||||
float[] heightTable,
|
||||
TerrainAtlas atlas);
|
||||
Shader CreateMeshShader(GL gl, string shadersDirectory);
|
||||
WbMeshAdapter CreateMeshAdapter(
|
||||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
IGpuResourceRetirementQueue retirement);
|
||||
TextureCache CreateTextureCache(
|
||||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
BindlessSupport bindless,
|
||||
IGpuResourceRetirementQueue retirement);
|
||||
SamplerCache CreateSamplerCache(GL gl);
|
||||
void Release(IDisposable resource);
|
||||
}
|
||||
|
||||
internal sealed class RetailWorldRenderCompositionFactory
|
||||
: IWorldRenderCompositionFactory
|
||||
{
|
||||
public void InitializeGlState(GL gl)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(gl);
|
||||
gl.ClearColor(0.05f, 0.10f, 0.18f, 1.0f);
|
||||
gl.Enable(EnableCap.DepthTest);
|
||||
}
|
||||
|
||||
public WorldRegionData LoadRegion(IDatReaderWriter dats)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dats);
|
||||
Region region = dats.Get<Region>(0x13000000u)
|
||||
?? throw new InvalidOperationException(
|
||||
"Region dat id 0x13000000 missing");
|
||||
float[]? heightTable = region.LandDefs.LandHeightTable;
|
||||
if (heightTable is null || heightTable.Length < 256)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Region.LandDefs.LandHeightTable missing or truncated");
|
||||
}
|
||||
return new WorldRegionData(region, heightTable);
|
||||
}
|
||||
|
||||
public void InitializeEnvironment(
|
||||
WorldEnvironmentController environment,
|
||||
Region region)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(environment);
|
||||
ArgumentNullException.ThrowIfNull(region);
|
||||
environment.Initialize(region);
|
||||
}
|
||||
|
||||
public BindlessSupport RequireBindless(GL gl, Action<string> log)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(gl);
|
||||
ArgumentNullException.ThrowIfNull(log);
|
||||
if (BindlessSupport.TryCreate(gl, out BindlessSupport? bindless))
|
||||
{
|
||||
if (bindless!.HasShaderDrawParameters(gl))
|
||||
{
|
||||
log("[N.5] modern path capabilities present " +
|
||||
"(bindless + ARB_shader_draw_parameters)");
|
||||
return bindless;
|
||||
}
|
||||
log("[N.5] GL_ARB_shader_draw_parameters not present — " +
|
||||
"modern path not available");
|
||||
}
|
||||
else
|
||||
{
|
||||
log("[N.5] GL_ARB_bindless_texture not present — " +
|
||||
"modern path not available");
|
||||
}
|
||||
|
||||
throw new NotSupportedException(
|
||||
"acdream requires GL_ARB_bindless_texture + " +
|
||||
"GL_ARB_shader_draw_parameters (GL 4.3+ with bindless support). " +
|
||||
"Your GPU/driver does not expose these extensions. If this is " +
|
||||
"unexpected, please file a bug report with your GPU vendor + " +
|
||||
"driver version.");
|
||||
}
|
||||
|
||||
public TerrainAtlas AcquireTerrainAtlas(
|
||||
IGameRenderResourceLifetime lifetime,
|
||||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
BindlessSupport bindless)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(lifetime);
|
||||
return lifetime.AcquireTerrainAtlas(
|
||||
() => TerrainAtlas.Build(gl, dats, bindless));
|
||||
}
|
||||
|
||||
public void SetTerrainAnisotropic(TerrainAtlas atlas, int level) =>
|
||||
atlas.SetAnisotropic(level);
|
||||
|
||||
public Shader CreateTerrainShader(GL gl, string shadersDirectory) =>
|
||||
new(
|
||||
gl,
|
||||
Path.Combine(shadersDirectory, "terrain_modern.vert"),
|
||||
Path.Combine(shadersDirectory, "terrain_modern.frag"));
|
||||
|
||||
public SceneLightingUboBinding CreateSceneLighting(GL gl) => new(gl);
|
||||
|
||||
public DebugLineRenderer CreateDebugLines(
|
||||
GL gl,
|
||||
string shadersDirectory) =>
|
||||
new(gl, shadersDirectory);
|
||||
|
||||
public byte[]? TryLoadDebugFont() =>
|
||||
BitmapFont.TryLoadSystemMonospaceFont();
|
||||
|
||||
public BitmapFont CreateDebugFont(GL gl, byte[] bytes) =>
|
||||
new(gl, bytes, pixelHeight: 15f, atlasSize: 512);
|
||||
|
||||
public TextRenderer CreateTextRenderer(
|
||||
GL gl,
|
||||
string shadersDirectory) =>
|
||||
new(gl, shadersDirectory);
|
||||
|
||||
public TerrainModernRenderer CreateTerrain(
|
||||
GL gl,
|
||||
BindlessSupport bindless,
|
||||
Shader shader,
|
||||
TerrainAtlas atlas,
|
||||
IGpuResourceRetirementQueue retirement) =>
|
||||
new(gl, bindless, shader, atlas, retirement);
|
||||
|
||||
public WorldTerrainBuildContext CreateTerrainBuildContext(
|
||||
uint initialCenterLandblockId,
|
||||
float[] heightTable,
|
||||
TerrainAtlas atlas)
|
||||
{
|
||||
int centerX = (int)((initialCenterLandblockId >> 24) & 0xFFu);
|
||||
int centerY = (int)((initialCenterLandblockId >> 16) & 0xFFu);
|
||||
var layers = new Dictionary<uint, byte>(atlas.TerrainTypeToLayer.Count);
|
||||
foreach ((uint terrainType, uint layer) in atlas.TerrainTypeToLayer)
|
||||
layers[terrainType] = (byte)layer;
|
||||
|
||||
const uint RoadTypeEnumValue = 0x20;
|
||||
byte roadLayer = layers.TryGetValue(RoadTypeEnumValue, out byte road)
|
||||
? road
|
||||
: SurfaceInfo.None;
|
||||
var blending = new TerrainBlendingContext(
|
||||
TerrainTypeToLayer: layers,
|
||||
RoadLayer: roadLayer,
|
||||
CornerAlphaLayers: atlas.CornerAlphaLayers,
|
||||
SideAlphaLayers: atlas.SideAlphaLayers,
|
||||
RoadAlphaLayers: atlas.RoadAlphaLayers,
|
||||
CornerAlphaTCodes: atlas.CornerAlphaTCodes,
|
||||
SideAlphaTCodes: atlas.SideAlphaTCodes,
|
||||
RoadAlphaRCodes: atlas.RoadAlphaRCodes);
|
||||
return new WorldTerrainBuildContext(
|
||||
initialCenterLandblockId,
|
||||
centerX,
|
||||
centerY,
|
||||
heightTable,
|
||||
blending,
|
||||
new ConcurrentDictionary<uint, SurfaceInfo>());
|
||||
}
|
||||
|
||||
public Shader CreateMeshShader(GL gl, string shadersDirectory) =>
|
||||
new(
|
||||
gl,
|
||||
Path.Combine(shadersDirectory, "mesh_modern.vert"),
|
||||
Path.Combine(shadersDirectory, "mesh_modern.frag"));
|
||||
|
||||
public WbMeshAdapter CreateMeshAdapter(
|
||||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
IGpuResourceRetirementQueue retirement) =>
|
||||
new(
|
||||
gl,
|
||||
dats,
|
||||
NullLogger<WbMeshAdapter>.Instance,
|
||||
retirement);
|
||||
|
||||
public TextureCache CreateTextureCache(
|
||||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
BindlessSupport bindless,
|
||||
IGpuResourceRetirementQueue retirement) =>
|
||||
new(gl, dats, bindless, retirement);
|
||||
|
||||
public SamplerCache CreateSamplerCache(GL gl) => new(gl);
|
||||
|
||||
public void Release(IDisposable resource) => resource.Dispose();
|
||||
}
|
||||
|
||||
internal enum WorldRenderCompositionPoint
|
||||
{
|
||||
GlStateInitialized,
|
||||
RegionLoaded,
|
||||
EnvironmentInitialized,
|
||||
BindlessPublished,
|
||||
TerrainAtlasAcquired,
|
||||
TerrainShaderPublished,
|
||||
SceneLightingPublished,
|
||||
DebugLinesPublished,
|
||||
DebugFontCreated,
|
||||
TextRendererCreated,
|
||||
HudResourcesPublished,
|
||||
HudResourcesCompleted,
|
||||
TerrainPublished,
|
||||
TerrainBuildStatePublished,
|
||||
MeshShaderPublished,
|
||||
MeshAdapterPublished,
|
||||
TextureCachePublished,
|
||||
SamplerCachePublished,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Production Phase 4. It preserves the mandatory modern renderer and sole
|
||||
/// DAT path while moving construction into an ordered, failure-injectable
|
||||
/// ownership boundary.
|
||||
/// </summary>
|
||||
internal sealed class WorldRenderCompositionPhase
|
||||
: IWorldRenderCompositionPhase<
|
||||
GameWindowPlatformResult<GL, IInputContext>,
|
||||
ContentEffectsAudioResult,
|
||||
SettingsDevToolsResult,
|
||||
WorldRenderResult>
|
||||
{
|
||||
private readonly WorldRenderDependencies _dependencies;
|
||||
private readonly IGameWindowWorldRenderPublication _publication;
|
||||
private readonly IWorldRenderCompositionFactory _factory;
|
||||
private readonly Action<WorldRenderCompositionPoint>? _faultInjection;
|
||||
|
||||
public WorldRenderCompositionPhase(
|
||||
WorldRenderDependencies dependencies,
|
||||
IGameWindowWorldRenderPublication publication,
|
||||
IWorldRenderCompositionFactory? factory = null,
|
||||
Action<WorldRenderCompositionPoint>? faultInjection = null)
|
||||
{
|
||||
_dependencies = dependencies
|
||||
?? throw new ArgumentNullException(nameof(dependencies));
|
||||
_publication = publication
|
||||
?? throw new ArgumentNullException(nameof(publication));
|
||||
_factory = factory ?? new RetailWorldRenderCompositionFactory();
|
||||
_faultInjection = faultInjection;
|
||||
}
|
||||
|
||||
public WorldRenderResult Compose(
|
||||
GameWindowPlatformResult<GL, IInputContext> platform,
|
||||
ContentEffectsAudioResult content,
|
||||
SettingsDevToolsResult settings)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(platform);
|
||||
ArgumentNullException.ThrowIfNull(content);
|
||||
ArgumentNullException.ThrowIfNull(settings);
|
||||
|
||||
var scope = new CompositionAcquisitionScope();
|
||||
try
|
||||
{
|
||||
GL gl = platform.Graphics;
|
||||
_factory.InitializeGlState(gl);
|
||||
Fault(WorldRenderCompositionPoint.GlStateInitialized);
|
||||
|
||||
WorldRegionData region = _factory.LoadRegion(content.Dats);
|
||||
Fault(WorldRenderCompositionPoint.RegionLoaded);
|
||||
_factory.InitializeEnvironment(_dependencies.Environment, region.Region);
|
||||
Fault(WorldRenderCompositionPoint.EnvironmentInitialized);
|
||||
|
||||
BindlessSupport bindless = _factory.RequireBindless(gl, _dependencies.Log);
|
||||
_publication.PublishBindlessSupport(bindless);
|
||||
Fault(WorldRenderCompositionPoint.BindlessPublished);
|
||||
|
||||
TerrainAtlas terrainAtlas = _factory.AcquireTerrainAtlas(
|
||||
_dependencies.RenderResources,
|
||||
gl,
|
||||
content.Dats,
|
||||
bindless);
|
||||
_factory.SetTerrainAnisotropic(
|
||||
terrainAtlas,
|
||||
settings.ResolvedQuality.AnisotropicLevel);
|
||||
Fault(WorldRenderCompositionPoint.TerrainAtlasAcquired);
|
||||
|
||||
string shadersDirectory = Path.Combine(
|
||||
AppContext.BaseDirectory,
|
||||
"Rendering",
|
||||
"Shaders");
|
||||
Shader terrainShader = AcquireAndPublish(
|
||||
scope,
|
||||
"terrain shader",
|
||||
() => _factory.CreateTerrainShader(gl, shadersDirectory),
|
||||
_publication.PublishTerrainShader,
|
||||
WorldRenderCompositionPoint.TerrainShaderPublished);
|
||||
SceneLightingUboBinding sceneLighting = AcquireAndPublish(
|
||||
scope,
|
||||
"scene lighting",
|
||||
() => _factory.CreateSceneLighting(gl),
|
||||
_publication.PublishSceneLighting,
|
||||
WorldRenderCompositionPoint.SceneLightingPublished);
|
||||
DebugLineRenderer debugLines = AcquireAndPublish(
|
||||
scope,
|
||||
"debug lines",
|
||||
() => _factory.CreateDebugLines(gl, shadersDirectory),
|
||||
_publication.PublishDebugLines,
|
||||
WorldRenderCompositionPoint.DebugLinesPublished);
|
||||
|
||||
(BitmapFont? debugFont, TextRenderer? textRenderer) =
|
||||
ComposeOptionalHudResources(scope, gl, shadersDirectory);
|
||||
|
||||
TerrainModernRenderer terrain = AcquireAndPublish(
|
||||
scope,
|
||||
"terrain renderer",
|
||||
() => _factory.CreateTerrain(
|
||||
gl,
|
||||
bindless,
|
||||
terrainShader,
|
||||
terrainAtlas,
|
||||
_dependencies.ResourceRetirement),
|
||||
_publication.PublishTerrain,
|
||||
WorldRenderCompositionPoint.TerrainPublished);
|
||||
|
||||
WorldTerrainBuildContext terrainBuild =
|
||||
_factory.CreateTerrainBuildContext(
|
||||
_dependencies.InitialCenterLandblockId,
|
||||
region.HeightTable,
|
||||
terrainAtlas);
|
||||
_publication.PublishTerrainBuildState(
|
||||
terrainBuild.HeightTable,
|
||||
terrainBuild.Blending,
|
||||
terrainBuild.SurfaceCache);
|
||||
Fault(WorldRenderCompositionPoint.TerrainBuildStatePublished);
|
||||
|
||||
Shader meshShader = AcquireAndPublish(
|
||||
scope,
|
||||
"mesh shader",
|
||||
() => _factory.CreateMeshShader(gl, shadersDirectory),
|
||||
_publication.PublishMeshShader,
|
||||
WorldRenderCompositionPoint.MeshShaderPublished);
|
||||
_dependencies.Log("[N.5] mesh_modern shader loaded");
|
||||
WbMeshAdapter meshAdapter = AcquireAndPublish(
|
||||
scope,
|
||||
"WB mesh adapter",
|
||||
() => _factory.CreateMeshAdapter(
|
||||
gl,
|
||||
content.Dats,
|
||||
_dependencies.ResourceRetirement),
|
||||
_publication.PublishWbMeshAdapter,
|
||||
WorldRenderCompositionPoint.MeshAdapterPublished);
|
||||
TextureCache textureCache = AcquireAndPublish(
|
||||
scope,
|
||||
"texture cache",
|
||||
() => _factory.CreateTextureCache(
|
||||
gl,
|
||||
content.Dats,
|
||||
bindless,
|
||||
_dependencies.ResourceRetirement),
|
||||
_publication.PublishTextureCache,
|
||||
WorldRenderCompositionPoint.TextureCachePublished);
|
||||
SamplerCache samplers = AcquireAndPublish(
|
||||
scope,
|
||||
"sampler cache",
|
||||
() => _factory.CreateSamplerCache(gl),
|
||||
_publication.PublishSamplerCache,
|
||||
WorldRenderCompositionPoint.SamplerCachePublished);
|
||||
|
||||
scope.Complete();
|
||||
_dependencies.Log(
|
||||
"[N.4+N.5] WB foundation + modern path active — " +
|
||||
"routing all content through ObjectMeshManager.");
|
||||
return new WorldRenderResult(
|
||||
terrainBuild,
|
||||
new WorldRenderFoundation(
|
||||
shadersDirectory,
|
||||
bindless,
|
||||
terrainAtlas,
|
||||
terrainShader,
|
||||
sceneLighting,
|
||||
debugLines,
|
||||
debugFont,
|
||||
textRenderer,
|
||||
terrain,
|
||||
meshShader,
|
||||
meshAdapter,
|
||||
textureCache,
|
||||
samplers));
|
||||
}
|
||||
catch (Exception failure)
|
||||
{
|
||||
scope.RollbackAndThrow(failure);
|
||||
throw new System.Diagnostics.UnreachableException();
|
||||
}
|
||||
}
|
||||
|
||||
private (BitmapFont? Font, TextRenderer? Text) ComposeOptionalHudResources(
|
||||
CompositionAcquisitionScope scope,
|
||||
GL gl,
|
||||
string shadersDirectory)
|
||||
{
|
||||
byte[]? fontBytes = _factory.TryLoadDebugFont();
|
||||
if (fontBytes is null)
|
||||
{
|
||||
_dependencies.Log("world-hud font: no system monospace font found");
|
||||
Fault(WorldRenderCompositionPoint.HudResourcesCompleted);
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
var fontLease = scope.Acquire(
|
||||
"world HUD font",
|
||||
() => _factory.CreateDebugFont(gl, fontBytes),
|
||||
_factory.Release);
|
||||
BitmapFont font = fontLease.Resource;
|
||||
Fault(WorldRenderCompositionPoint.DebugFontCreated);
|
||||
var textLease = scope.Acquire(
|
||||
"world HUD text renderer",
|
||||
() => _factory.CreateTextRenderer(gl, shadersDirectory),
|
||||
_factory.Release);
|
||||
TextRenderer text = textLease.Resource;
|
||||
Fault(WorldRenderCompositionPoint.TextRendererCreated);
|
||||
|
||||
_publication.PublishHudResources(font, text);
|
||||
fontLease.Transfer();
|
||||
textLease.Transfer();
|
||||
Fault(WorldRenderCompositionPoint.HudResourcesPublished);
|
||||
_dependencies.Log(
|
||||
$"world-hud font: loaded {fontBytes.Length / 1024}KB, " +
|
||||
$"atlas {font.AtlasWidth}x{font.AtlasHeight}, " +
|
||||
$"lineHeight={font.LineHeight:F1}px (reserved for D.6 HUD)");
|
||||
Fault(WorldRenderCompositionPoint.HudResourcesCompleted);
|
||||
return (font, text);
|
||||
}
|
||||
|
||||
private T AcquireAndPublish<T>(
|
||||
CompositionAcquisitionScope scope,
|
||||
string name,
|
||||
Func<T> factory,
|
||||
Action<T> publish,
|
||||
WorldRenderCompositionPoint point)
|
||||
where T : class, IDisposable
|
||||
{
|
||||
T value = scope.Acquire(name, factory, _factory.Release).Publish(publish);
|
||||
Fault(point);
|
||||
return value;
|
||||
}
|
||||
|
||||
private void Fault(WorldRenderCompositionPoint point) =>
|
||||
_faultInjection?.Invoke(point);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue