From 6ac4071f1bc9053d5487b0090664291cd8978fe2 Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Fri, 16 Feb 2024 00:24:52 +1000 Subject: [PATCH] EA --- .../voxy/client/core/model/ModelManager.java | 57 +++++++-- .../client/core/rendering/RenderTracker.java | 4 +- .../rendering/building/RenderDataFactory.java | 108 +++++++++++------- .../building/RenderGenerationService.java | 2 +- .../voxy/client/core/util/Mesher2D.java | 13 ++- .../minecraft/MixinClientChunkManager.java | 2 +- .../sodium/MixinRenderSectionManager.java | 25 ++++ .../other/TranslocatingStorageAdaptor.java | 42 +++++-- src/main/resources/voxy.mixins.json | 5 +- 9 files changed, 191 insertions(+), 67 deletions(-) create mode 100644 src/main/java/me/cortex/voxy/client/mixin/sodium/MixinRenderSectionManager.java diff --git a/src/main/java/me/cortex/voxy/client/core/model/ModelManager.java b/src/main/java/me/cortex/voxy/client/core/model/ModelManager.java index 6d7687b7..0d5799cf 100644 --- a/src/main/java/me/cortex/voxy/client/core/model/ModelManager.java +++ b/src/main/java/me/cortex/voxy/client/core/model/ModelManager.java @@ -3,9 +3,11 @@ package me.cortex.voxy.client.core.model; import com.mojang.blaze3d.platform.GlConst; import com.mojang.blaze3d.platform.GlStateManager; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import me.cortex.voxy.client.core.IGetVoxelCore; import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlTexture; import me.cortex.voxy.client.core.rendering.util.UploadStream; +import me.cortex.voxy.common.world.other.Mapper; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; @@ -53,9 +55,9 @@ import static org.lwjgl.opengl.GL45C.glTextureSubImage2D; public class ModelManager { //TODO: replace the fluid BlockState with a client model id integer of the fluidState, requires looking up // the fluid state in the mipper - private record ModelEntry(List textures, BlockState fluidBlockState){ - private ModelEntry(ColourDepthTextureData[] textures, BlockState fluidBlockState) { - this(Stream.of(textures).map(ColourDepthTextureData::clone).toList(), fluidBlockState); + private record ModelEntry(List textures, int fluidBlockStateId){ + private ModelEntry(ColourDepthTextureData[] textures, int fluidBlockStateId) { + this(Stream.of(textures).map(ColourDepthTextureData::clone).toList(), fluidBlockStateId); } } @@ -96,6 +98,7 @@ public class ModelManager { // this has an issue with scaffolding i believe tho, so maybe make it a probability to render??? idk private final long[] metadataCache; + private final int[] fluidStateLUT; //Provides a map from id -> model id as multiple ids might have the same internal model id private final int[] idMappings; @@ -116,8 +119,10 @@ public class ModelManager { //TODO: figure out how to do mipping :blobfox_pineapple: this.textures = new GlTexture().store(GL_RGBA8, 4, modelTextureSize*3*256,modelTextureSize*2*256); this.metadataCache = new long[1<<16]; + this.fluidStateLUT = new int[1<<16]; this.idMappings = new int[1<<20];//Max of 1 million blockstates mapping to 65k model states Arrays.fill(this.idMappings, -1); + Arrays.fill(this.fluidStateLUT, -1); glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); @@ -145,8 +150,24 @@ public class ModelManager { int modelId = -1; var textureData = this.bakery.renderFaces(blockState, 123456, isFluid); + int clientFluidStateId = -1; + + if ((!isFluid) && (!blockState.getFluidState().isEmpty())) { + //Insert into the fluid LUT + var fluidState = blockState.getFluidState().getBlockState(); + + //TODO:FIXME: PASS IN THE Mapper instead of grabbing it!!! THIS IS CRTICIAL TO FIX + int fluidStateId = ((IGetVoxelCore)MinecraftClient.getInstance().worldRenderer).getVoxelCore().getWorldEngine().getMapper().getIdForBlockState(fluidState); + + + clientFluidStateId = this.idMappings[fluidStateId]; + if (clientFluidStateId == -1) { + clientFluidStateId = this.addEntry(fluidStateId, fluidState); + } + } + {//Deduplicate same entries - var entry = new ModelEntry(textureData, isFluid||blockState.getFluidState().isEmpty()?null:blockState.getFluidState().getBlockState()); + var entry = new ModelEntry(textureData, clientFluidStateId); int possibleDuplicate = this.modelTexture2id.getInt(entry); if (possibleDuplicate != -1) {//Duplicate found this.idMappings[blockId] = possibleDuplicate; @@ -159,6 +180,12 @@ public class ModelManager { } } + if (isFluid) { + this.fluidStateLUT[modelId] = modelId; + } else if (clientFluidStateId != -1) { + this.fluidStateLUT[modelId] = clientFluidStateId; + } + var colourProvider = MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(blockState.getBlock())); @@ -205,6 +232,7 @@ public class ModelManager { metadata |= blockRenderLayer == RenderLayer.getTranslucent()?2:0; metadata |= needsDoubleSidedQuads?4:0; metadata |= (!blockState.getFluidState().isEmpty())?8:0;//Has a fluid state accosiacted with it + metadata |= isFluid?16:0;//Is a fluid //TODO: add a bunch of control config options for overriding/setting options of metadata for each face of each type for (int face = 5; face != -1; face--) {//In reverse order to make indexing into the metadata long easier @@ -464,11 +492,6 @@ public class ModelManager { return ((metadata>>(8*face))&0b1000) != 0; } - public static boolean isColoured(long metadata) { - //TODO: THIS - return false; - } - public static boolean isDoubleSided(long metadata) { return ((metadata>>(8*6))&4) != 0; } @@ -481,6 +504,10 @@ public class ModelManager { return ((metadata>>(8*6))&8) != 0; } + public static boolean isFluid(long metadata) { + return ((metadata>>(8*6))&16) != 0; + } + public static boolean isBiomeColoured(long metadata) { return ((metadata>>(8*6))&1) != 0; } @@ -538,6 +565,10 @@ public class ModelManager { //} } + public long getModelMetadataFromClientId(int clientId) { + return this.metadataCache[clientId]; + } + public int getModelId(int blockId) { int map = this.idMappings[blockId]; if (map == -1) { @@ -546,6 +577,14 @@ public class ModelManager { return map; } + public int getFluidClientStateId(int clientBlockStateId) { + int map = this.fluidStateLUT[clientBlockStateId]; + if (map == -1) { + throw new IdNotYetComputedException(clientBlockStateId); + } + return map; + } + private void putTextures(int id, ColourDepthTextureData[] textures) { int X = (id&0xFF) * this.modelTextureSize*3; int Y = ((id>>8)&0xFF) * this.modelTextureSize*2; diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/RenderTracker.java b/src/main/java/me/cortex/voxy/client/core/rendering/RenderTracker.java index 0f51fad4..7fd29de8 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/RenderTracker.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/RenderTracker.java @@ -141,8 +141,8 @@ public class RenderTracker { //Enqueues a renderTask for a section to cache the result public void addCache(int lvl, int x, int y, int z) { - this.renderGen.markCache(lvl, x, y, z); - this.renderGen.enqueueTask(lvl, x, y, z, ((lvl1, x1, y1, z1) -> true));//TODO: replace the true identity lambda with a callback check to the render cache + //this.renderGen.markCache(lvl, x, y, z); + //this.renderGen.enqueueTask(lvl, x, y, z, ((lvl1, x1, y1, z1) -> true));//TODO: replace the true identity lambda with a callback check to the render cache } //Removes the position from the cache diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory.java index 80171555..5c529bd5 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory.java @@ -20,6 +20,8 @@ public class RenderDataFactory { private final Mesher2D negativeMesher = new Mesher2D(5, 15); private final Mesher2D positiveMesher = new Mesher2D(5, 15); + private final Mesher2D negativeFluidMesher = new Mesher2D(5, 15); + private final Mesher2D positiveFluidMesher = new Mesher2D(5, 15); private final long[] sectionCache = new long[32*32*32]; private final long[] connectedSectionCache = new long[32*32*32]; @@ -45,10 +47,12 @@ public class RenderDataFactory { // instead of needing to regen the entire thing - //section is already acquired and gets released by the parent + //Ok so the idea for fluid rendering is to make it use a seperate mesher and use a different code path for it + // since fluid states are explicitly overlays over the base block + // can do funny stuff like double rendering - //buildMask in the lower 6 bits contains the faces to build, the next 6 bits are whether the edge face builds against - // its neigbor or not (0 if it does 1 if it doesnt (0 is default behavior)) + + //section is already acquired and gets released by the parent public BuiltSection generateMesh(WorldSection section) { section.copyDataTo(this.sectionCache); this.translucentQuadCollector.clear(); @@ -116,6 +120,7 @@ public class RenderDataFactory { } + //TODO: FIXME: a block can have a face even if it doesnt, cause of if it has a fluid state private void generateMeshForAxis(WorldSection section, int axisId) { int aX = axisId==2?1:0; int aY = axisId==0?1:0; @@ -130,6 +135,8 @@ public class RenderDataFactory { for (int primary = 0; primary < 32; primary++) { this.negativeMesher.reset(); this.positiveMesher.reset(); + this.negativeFluidMesher.reset(); + this.positiveFluidMesher.reset(); for (int a = 0; a < 32; a++) { for (int b = 0; b < 32; b++) { @@ -147,8 +154,7 @@ public class RenderDataFactory { boolean putFace = false; //Branch into 2 paths, the + direction and -direction, doing it at once makes it much faster as it halves the number of loops - - if (ModelManager.faceExists(selfMetadata, axisId<<1)) {//- direction + if (ModelManager.faceExists(selfMetadata, axisId<<1) || ModelManager.containsFluid(selfMetadata)) {//- direction long facingState = Mapper.AIR; //Need to access the other connecting section if (primary == 0) { @@ -167,9 +173,14 @@ public class RenderDataFactory { facingState = this.sectionCache[WorldSection.getIndex(x-aX, y-aY, z-aZ)]; } - putFace |= this.putFaceIfCan(this.negativeMesher, (axisId<<1), (axisId<<1)|1, self, selfMetadata, selfBlockId, facingState, a, b); + if (!ModelManager.isFluid(selfMetadata)) { + putFace |= this.putFaceIfCan(this.negativeMesher, (axisId << 1), (axisId << 1)|1, self, selfMetadata, selfBlockId, facingState, a, b); + } + if (ModelManager.containsFluid(selfMetadata)) { + putFace |= this.putFluidFaceIfCan(this.negativeFluidMesher, (axisId << 1), (axisId << 1)|1, self, selfMetadata, selfBlockId, facingState, a, b); + } } - if (ModelManager.faceExists(selfMetadata, axisId<<1)) {//+ direction + if (ModelManager.faceExists(selfMetadata, (axisId<<1)|1) || ModelManager.containsFluid(selfMetadata)) {//+ direction long facingState = Mapper.AIR; //Need to access the other connecting section if (primary == 31) { @@ -187,8 +198,12 @@ public class RenderDataFactory { } else { facingState = this.sectionCache[WorldSection.getIndex(x+aX, y+aY, z+aZ)]; } - - putFace |= this.putFaceIfCan(this.positiveMesher, (axisId<<1)|1, (axisId<<1), self, selfMetadata, selfBlockId, facingState, a, b); + if (!ModelManager.isFluid(selfMetadata)) { + putFace |= this.putFaceIfCan(this.positiveMesher, (axisId << 1) | 1, (axisId << 1), self, selfMetadata, selfBlockId, facingState, a, b); + } + if (ModelManager.containsFluid(selfMetadata)) { + putFace |= this.putFluidFaceIfCan(this.positiveFluidMesher, (axisId << 1) | 1, (axisId << 1), self, selfMetadata, selfBlockId, facingState, a, b); + } } if (putFace) { @@ -204,9 +219,54 @@ public class RenderDataFactory { processMeshedFace(this.negativeMesher, axisId<<1, primary, this.directionalQuadCollectors[(axisId<<1)]); processMeshedFace(this.positiveMesher, (axisId<<1)|1, primary, this.directionalQuadCollectors[(axisId<<1)|1]); + + processMeshedFace(this.negativeFluidMesher, axisId<<1, primary, this.directionalQuadCollectors[(axisId<<1)]); + processMeshedFace(this.positiveFluidMesher, (axisId<<1)|1, primary, this.directionalQuadCollectors[(axisId<<1)|1]); } } + + + //Returns true if a face was placed + private boolean putFluidFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int selfBlockId, long facingState, int a, int b) { + long facingMetadata = this.modelMan.getModelMetadata(Mapper.getBlockId(facingState)); + + int selfFluidClientId = this.modelMan.getFluidClientStateId(this.modelMan.getModelId(selfBlockId)); + long selfFluidMetadata = this.modelMan.getModelMetadataFromClientId(selfFluidClientId); + + int facingFluidClientId = -1; + if (ModelManager.containsFluid(facingMetadata)) { + facingFluidClientId = this.modelMan.getFluidClientStateId(this.modelMan.getModelId(Mapper.getBlockId(facingState))); + } + + //If both of the states are the same, then dont render the fluid face + if (selfFluidClientId == facingFluidClientId) { + return false; + } + + if (facingFluidClientId != -1) { + if (this.world.getMapper().getBlockStateFromId(selfBlockId).getFluidState().getFluid() == this.world.getMapper().getBlockStateFromId(facingState).getFluidState().getFluid()) { + return false; + } + } + + if (ModelManager.faceOccludes(facingMetadata, opposingFace)) { + return false; + } + + //NOTE: if the model has a fluid state but is not a liquid need to see if the solid state had a face rendered and that face is occluding, if so, dont render the fluid state face + + //TODO:FIXME FINISH + + + + long otherFlags = 0; + otherFlags |= ModelManager.isTranslucent(selfFluidMetadata)?1L<<33:0; + otherFlags |= ModelManager.isDoubleSided(selfFluidMetadata)?1L<<34:0; + mesher.put(a, b, ((long)selfFluidClientId) | (((long) Mapper.getLightId(ModelManager.faceUsesSelfLighting(selfFluidMetadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelManager.isBiomeColoured(selfFluidMetadata)?1:0)) | otherFlags); + return true; + } + //Returns true if a face was placed private boolean putFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int selfBlockId, long facingState, int a, int b) { long facingMetadata = this.modelMan.getModelMetadata(Mapper.getBlockId(facingState)); @@ -222,36 +282,6 @@ public class RenderDataFactory { } - //TODO: if the model has a fluid state, it should compute if a fluid face needs to be injected - // fluid face of type this.world.getMapper().getBlockStateFromId(self).getFluidState() and block type - // this.world.getMapper().getBlockStateFromId(self).getFluidState().getBlockState() - - //If we are a fluid - if (ModelManager.containsFluid(metadata)) { - var selfBS = this.world.getMapper().getBlockStateFromId(self); - if (ModelManager.containsFluid(facingMetadata)) {//and the oposing face is also a fluid need to make a closer check - var faceBS = this.world.getMapper().getBlockStateFromId(facingState); - - //If we are a fluid block that means our face is a fluid face, waterlogged blocks dont include fluid faces in the model data - if (selfBS.getBlock() instanceof FluidBlock) { - //If the fluid state of both blocks are the same we dont emit extra geometry - if (selfBS.getFluidState().getBlockState().equals(faceBS.getFluidState().getBlockState())) { - return false; - } - } else {//If we are not a fluid block, we might need to emit extra geometry (fluid faces) to the auxliery mesher - boolean shouldEmitFluidFace = !selfBS.getFluidState().getBlockState().equals(faceBS.getFluidState().getBlockState()); - //TODO: THIS - int aa = 0; - } - } else if (!(selfBS.getBlock() instanceof FluidBlock)) {//If we are not a fluid block but we contain a fluid we might need to emit extra geometry - //Basicly need to get the fluid state and run putFaceIfCan using the fluid state as the self state and keep the same facing state - //TODO: THIS - int aa = 0; - } - } - - - int clientModelId = this.modelMan.getModelId(selfBlockId); long otherFlags = 0; otherFlags |= ModelManager.isTranslucent(metadata)?1L<<33:0; diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java index edb914c5..f30e7150 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java @@ -62,7 +62,7 @@ public class RenderGenerationService { mesh = factory.generateMesh(section); } catch (IdNotYetComputedException e) { //We need to reinsert the build task into the queue - System.err.println("Render task failed to complete due to un-computed client id"); + //System.err.println("Render task failed to complete due to un-computed client id"); synchronized (this.taskQueue) { this.taskQueue.computeIfAbsent(section.key, key->{this.taskCounter.release(); return task;}); } diff --git a/src/main/java/me/cortex/voxy/client/core/util/Mesher2D.java b/src/main/java/me/cortex/voxy/client/core/util/Mesher2D.java index b62598d2..ae22dd84 100644 --- a/src/main/java/me/cortex/voxy/client/core/util/Mesher2D.java +++ b/src/main/java/me/cortex/voxy/client/core/util/Mesher2D.java @@ -10,6 +10,7 @@ public class Mesher2D { private final long[] data; private final BitSet setset; private int[] quadCache; + private boolean isEmpty = true; public Mesher2D(int sizeBits, int maxSize) { this.size = sizeBits; this.maxSize = maxSize; @@ -27,6 +28,7 @@ public class Mesher2D { } public Mesher2D put(int x, int z, long data) { + this.isEmpty = false; int idx = this.getIdx(x, z); this.data[idx] = data; this.setset.set(idx); @@ -61,6 +63,10 @@ public class Mesher2D { //Returns the number of compacted quads public int process() { + if (this.isEmpty) { + return 0; + } + int[] quads = this.quadCache; int idxCount = 0; @@ -144,8 +150,11 @@ public class Mesher2D { } public void reset() { - this.setset.clear(); - Arrays.fill(this.data, 0); + if (!this.isEmpty) { + this.isEmpty = true; + this.setset.clear(); + Arrays.fill(this.data, 0); + } } public long getDataFromQuad(int quad) { diff --git a/src/main/java/me/cortex/voxy/client/mixin/minecraft/MixinClientChunkManager.java b/src/main/java/me/cortex/voxy/client/mixin/minecraft/MixinClientChunkManager.java index 961b7ac5..c46a5cc0 100644 --- a/src/main/java/me/cortex/voxy/client/mixin/minecraft/MixinClientChunkManager.java +++ b/src/main/java/me/cortex/voxy/client/mixin/minecraft/MixinClientChunkManager.java @@ -23,7 +23,7 @@ public class MixinClientChunkManager { private void injectUnload(ChunkPos pos, CallbackInfo ci, int index, WorldChunk worldChunk) { var core = ((IGetVoxelCore)(world.worldRenderer)).getVoxelCore(); if (core != null && VoxyConfig.CONFIG.ingestEnabled) { - core.enqueueIngest(worldChunk); + //core.enqueueIngest(worldChunk); } } } diff --git a/src/main/java/me/cortex/voxy/client/mixin/sodium/MixinRenderSectionManager.java b/src/main/java/me/cortex/voxy/client/mixin/sodium/MixinRenderSectionManager.java new file mode 100644 index 00000000..8fdcf1cc --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/mixin/sodium/MixinRenderSectionManager.java @@ -0,0 +1,25 @@ +package me.cortex.voxy.client.mixin.sodium; + +import me.cortex.voxy.client.config.VoxyConfig; +import me.cortex.voxy.client.core.IGetVoxelCore; +import me.jellysquid.mods.sodium.client.render.chunk.RenderSectionManager; +import net.minecraft.client.world.ClientWorld; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = RenderSectionManager.class, remap = false) +public class MixinRenderSectionManager { + @Shadow @Final private ClientWorld world; + + @Inject(method = "onChunkRemoved", at = @At("HEAD")) + private void injectIngest(int x, int z, CallbackInfo ci) { + var core = ((IGetVoxelCore)(world.worldRenderer)).getVoxelCore(); + if (core != null && VoxyConfig.CONFIG.ingestEnabled) { + core.enqueueIngest(world.getChunk(x, z)); + } + } +} diff --git a/src/main/java/me/cortex/voxy/common/storage/other/TranslocatingStorageAdaptor.java b/src/main/java/me/cortex/voxy/common/storage/other/TranslocatingStorageAdaptor.java index 079f8f2a..53df8346 100644 --- a/src/main/java/me/cortex/voxy/common/storage/other/TranslocatingStorageAdaptor.java +++ b/src/main/java/me/cortex/voxy/common/storage/other/TranslocatingStorageAdaptor.java @@ -10,7 +10,15 @@ import java.util.ArrayList; import java.util.List; public class TranslocatingStorageAdaptor extends DelegatingStorageAdaptor { - public record BoxTransform(int x1, int y1, int z1, int x2, int y2, int z2, int dx, int dy, int dz) { + public enum Mode { + BOX_ONLY, + PRIORITY_BOX, + PRIORITY_ORIGINAL + } + public record BoxTransform(int x1, int y1, int z1, int x2, int y2, int z2, int dx, int dy, int dz, Mode mode) { + public BoxTransform(int x1, int y1, int z1, int x2, int y2, int z2, int dx, int dy, int dz) { + this(x1, y1, z1, x2, y2, z2, dx, dy, dz, Mode.BOX_ONLY); + } public long transformIfInBox(long pos) { int lvl = WorldEngine.getLevel(pos); int x = WorldEngine.getX(pos); @@ -38,19 +46,29 @@ public class TranslocatingStorageAdaptor extends DelegatingStorageAdaptor { this.transforms = transforms; } - private long transformPosition(long pos) { - for (var transform : this.transforms) { - long tpos = transform.transformIfInBox(pos); - if (tpos != -1) { - return tpos; - } - } - return pos; - } - @Override public ByteBuffer getSectionData(long key) { - return super.getSectionData(this.transformPosition(key)); + for (var transform : this.transforms) { + long tpos = transform.transformIfInBox(key); + if (tpos != -1) { + if (transform.mode == Mode.BOX_ONLY || transform.mode == null) { + return super.getSectionData(tpos); + } else if (transform.mode == Mode.PRIORITY_BOX) { + var data = super.getSectionData(tpos); + if (data == null) { + return super.getSectionData(key); + } + } else if (transform.mode == Mode.PRIORITY_ORIGINAL) { + var data = super.getSectionData(key); + if (data == null) { + return super.getSectionData(tpos); + } + } else { + throw new IllegalStateException(); + } + } + } + return super.getSectionData(key); } @Override diff --git a/src/main/resources/voxy.mixins.json b/src/main/resources/voxy.mixins.json index e1847197..0f80992e 100644 --- a/src/main/resources/voxy.mixins.json +++ b/src/main/resources/voxy.mixins.json @@ -14,5 +14,8 @@ ], "injectors": { "defaultRequire": 1 - } + }, + "mixins": [ + "sodium.MixinRenderSectionManager" + ] }