22// Created by yechentide on 2025/09/19
33//
44
5- struct MCSubChunkStorage {
6- let version : Int
7- let chunkY : Int8
5+ public struct MCSubChunkStorage {
6+ public let version : Int
7+ public let chunkY : Int8
88
9- let blockLayer : BlockStorageLayer
10- let liquidLayer : BlockStorageLayer
9+ public let blockLayer : BlockStorageLayer
10+ public let liquidLayer : BlockStorageLayer
1111
12- struct BlockStorageLayer : PaletteReadable {
13- let bitWidth : Int
14- let palette : [ CompoundTag ]
15- let indicesBytes : [ UInt8 ]
12+ public struct BlockStorageLayer : PaletteReadable {
13+ public var bitWidth : Int
14+ public private( set) var palette : [ CompoundTag ]
15+ public private( set) var indicesBytes : [ UInt8 ]
16+ private var paletteIndexCache : [ Int : [ Int ] ]
1617
17- func paletteValue( at linearIndex: Int ) -> CompoundTag ? {
18+ public init ( bitWidth: Int , palette: [ CompoundTag ] , indicesBytes: [ UInt8 ] ) {
19+ self . bitWidth = bitWidth
20+ self . palette = palette
21+ self . indicesBytes = indicesBytes
22+ self . paletteIndexCache = [ : ]
23+ }
24+
25+ public func paletteValue( at linearIndex: Int ) -> CompoundTag ? {
1826 guard 0 ..< MCSubChunk . totalBlockCount ~= linearIndex else {
1927 return nil
2028 }
@@ -26,7 +34,7 @@ struct MCSubChunkStorage {
2634 }
2735
2836 @inline ( __always)
29- func unsafeEnumerateColumnDescendingY(
37+ internal func unsafeEnumerateColumnDescendingY(
3038 atLocalX localX: Int , localZ: Int , _ perform: ( Int , CompoundTag ) -> Bool
3139 ) {
3240 guard let baseIndex = MCSubChunk . linearIndex ( localX, 0 , localZ) else {
@@ -63,5 +71,113 @@ struct MCSubChunkStorage {
6371 }
6472 }
6573 }
74+
75+ // MARK: - Public API for Palette Management and Block Placement
76+
77+ public mutating func ensurePaletteIndex( for block: CompoundTag ) -> Int {
78+ rebuildCacheIfNeeded ( )
79+
80+ let hash = blockHash ( block)
81+ if let candidates = paletteIndexCache [ hash] {
82+ for index in candidates {
83+ if palette [ index] == block {
84+ return index
85+ }
86+ }
87+ }
88+
89+ // Not found, add to palette
90+ let newIndex = palette. count
91+ palette. append ( block)
92+ paletteIndexCache [ hash, default: [ ] ] . append ( newIndex)
93+ return newIndex
94+ }
95+
96+ public mutating func place( at localX: Int , localY: Int , localZ: Int , paletteIndex: Int ) {
97+ guard let linear = MCSubChunk . linearIndex ( localX, localY, localZ) else { return }
98+ guard paletteIndex < palette. count else { return }
99+
100+ ensureBitWidthCanHold ( paletteIndex)
101+ writePaletteIndex ( paletteIndex, atLinearIndex: linear)
102+ }
103+
104+ // MARK: - Private Helpers
105+
106+ private func blockHash( _ block: CompoundTag ) -> Int {
107+ block. description. hashValue
108+ }
109+
110+ private mutating func rebuildCacheIfNeeded( ) {
111+ guard paletteIndexCache. isEmpty else { return }
112+
113+ var cache : [ Int : [ Int ] ] = [ : ]
114+ for (index, block) in palette. enumerated ( ) {
115+ let hash = blockHash ( block)
116+ cache [ hash, default: [ ] ] . append ( index)
117+ }
118+ paletteIndexCache = cache
119+ }
120+
121+ private mutating func ensureBitWidthCanHold( _ index: Int ) {
122+ guard index >= ( 1 << bitWidth) else { return }
123+
124+ var newBitWidth = bitWidth
125+ while index >= ( 1 << newBitWidth) {
126+ newBitWidth += 1
127+ }
128+
129+ repackIndices ( to: newBitWidth)
130+ }
131+
132+ private mutating func repackIndices( to newBitWidth: Int ) {
133+ guard newBitWidth != bitWidth else { return }
134+
135+ let newValuesPerWord = 32 / newBitWidth
136+ let newWordCount = ( MCSubChunk . totalBlockCount + newValuesPerWord - 1 ) / newValuesPerWord
137+ var newIndicesBytes = [ UInt8] ( repeating: 0 , count: newWordCount * 4 )
138+
139+ for linearIndex in 0 ..< MCSubChunk . totalBlockCount {
140+ let existingIndex = paletteIndex ( at: linearIndex)
141+ writePaletteIndex ( existingIndex, atLinearIndex: linearIndex, using: newBitWidth, into: & newIndicesBytes)
142+ }
143+
144+ bitWidth = newBitWidth
145+ indicesBytes = newIndicesBytes
146+ paletteIndexCache = [ : ] // Invalidate cache after repack
147+ }
148+
149+ private func paletteIndex( at linearIndex: Int ) -> Int {
150+ let valuesPerWord = 32 / bitWidth
151+ let wordIndex = linearIndex / valuesPerWord
152+ let indexInWord = linearIndex % valuesPerWord
153+ let offset = indexInWord * bitWidth
154+ let mask = ( 1 << bitWidth) - 1
155+
156+ return indicesBytes. withUnsafeBytes { rawBuffer in
157+ let words = rawBuffer. baseAddress!. assumingMemoryBound ( to: UInt32 . self)
158+ let word = UInt32 ( littleEndian: words [ wordIndex] )
159+ return Int ( ( word >> offset) & UInt32 ( mask) )
160+ }
161+ }
162+
163+ private mutating func writePaletteIndex( _ index: Int , atLinearIndex linear: Int ) {
164+ writePaletteIndex ( index, atLinearIndex: linear, using: bitWidth, into: & indicesBytes)
165+ }
166+
167+ private func writePaletteIndex( _ index: Int , atLinearIndex linear: Int , using bw: Int , into bytes: inout [ UInt8 ] ) {
168+ let valuesPerWord = 32 / bw
169+ let wordIndex = linear / valuesPerWord
170+ let indexInWord = linear % valuesPerWord
171+ let offset = indexInWord * bw
172+ let mask = UInt32 ( ( 1 << bw) - 1 )
173+
174+ bytes. withUnsafeMutableBytes { rawBuffer in
175+ let words = rawBuffer. baseAddress!. assumingMemoryBound ( to: UInt32 . self)
176+ var word = UInt32 ( littleEndian: words [ wordIndex] )
177+ word &= ~ ( mask << offset) // Clear bits
178+ word |= UInt32 ( index) << offset // Set new bits
179+ words [ wordIndex] = word. littleEndian
180+ }
181+ }
66182 }
67183}
0 commit comments