@@ -10,10 +10,14 @@ namespace SharpCoreDB.Storage;
1010using System . Buffers ;
1111using System . Collections . Generic ;
1212using System . Diagnostics ;
13+ using System . Linq ;
1314using System . Runtime . InteropServices ;
1415using System . Threading ;
1516using System . Threading . Tasks ;
1617
18+ // ✅ SCDB Phase 2: Public type alias for user-facing API
19+ using Extent = SharpCoreDB . Storage . Scdb . FreeExtent ;
20+
1721/// <summary>
1822/// Free Space Map (FSM) for O(1) page allocation.
1923/// Uses two-level bitmap inspired by PostgreSQL.
@@ -30,6 +34,10 @@ internal sealed class FreeSpaceManager : IDisposable
3034 private readonly BitArray _l1Bitmap ; // 1 bit per page
3135 private readonly List < FreeExtent > _l2Extents ; // Large free extents
3236 private readonly Lock _allocationLock = new ( ) ;
37+
38+ // ✅ SCDB Phase 2: ExtentAllocator for optimized extent allocation
39+ private readonly ExtentAllocator _extentAllocator ;
40+
3341 private bool _isDirty ;
3442 private bool _disposed ;
3543 private ulong _totalPages ;
@@ -51,10 +59,130 @@ public FreeSpaceManager(SingleFileStorageProvider provider, ulong fsmOffset, ulo
5159 _totalPages = 0 ;
5260 _freePages = 0 ;
5361
62+ // ✅ SCDB Phase 2: Initialize ExtentAllocator
63+ _extentAllocator = new ExtentAllocator
64+ {
65+ Strategy = AllocationStrategy . BestFit // Default: minimize fragmentation
66+ } ;
67+
5468 // Load existing FSM from disk
5569 LoadFsm ( ) ;
5670 }
5771
72+ // ========================================
73+ // ✅ SCDB Phase 2: Public API for page/extent allocation
74+ // ========================================
75+
76+ /// <summary>
77+ /// Allocates a single page.
78+ /// </summary>
79+ /// <returns>Page ID of allocated page.</returns>
80+ public ulong AllocatePage ( )
81+ {
82+ var offset = AllocatePages ( 1 ) ;
83+ return offset / ( ulong ) _pageSize ; // Convert byte offset to page ID
84+ }
85+
86+ /// <summary>
87+ /// Allocates a contiguous extent of pages.
88+ /// ✅ SCDB Phase 2: Uses ExtentAllocator for optimized allocation.
89+ /// </summary>
90+ /// <param name="pageCount">Number of pages to allocate.</param>
91+ /// <returns>Extent representing the allocated pages.</returns>
92+ public Extent AllocateExtent ( int pageCount )
93+ {
94+ ArgumentOutOfRangeException . ThrowIfNegativeOrZero ( pageCount ) ;
95+
96+ lock ( _allocationLock )
97+ {
98+ // ✅ Try to allocate from existing free extents first (O(log n))
99+ var existingExtent = _extentAllocator . Allocate ( pageCount ) ;
100+
101+ if ( existingExtent . HasValue )
102+ {
103+ // Found suitable extent, mark pages as allocated
104+ for ( var i = 0UL ; i < ( ulong ) pageCount ; i ++ )
105+ {
106+ _l1Bitmap . Set ( ( int ) ( existingExtent . Value . StartPage + i ) , true ) ;
107+ }
108+
109+ _freePages -= ( ulong ) pageCount ;
110+ _isDirty = true ;
111+
112+ return new Extent ( existingExtent . Value . StartPage , ( ulong ) pageCount ) ;
113+ }
114+
115+ // No suitable extent found, allocate new pages
116+ var offset = AllocatePages ( pageCount ) ;
117+ var startPage = offset / ( ulong ) _pageSize ;
118+
119+ return new Extent ( startPage , ( ulong ) pageCount ) ;
120+ }
121+ }
122+
123+ /// <summary>
124+ /// Frees a single page.
125+ /// </summary>
126+ /// <param name="pageId">Page ID to free.</param>
127+ public void FreePage ( ulong pageId )
128+ {
129+ var offset = pageId * ( ulong ) _pageSize ;
130+ FreePages ( offset , 1 ) ;
131+ }
132+
133+ /// <summary>
134+ /// Frees an extent of pages.
135+ /// ✅ SCDB Phase 2: Uses ExtentAllocator for coalescing.
136+ /// </summary>
137+ /// <param name="extent">Extent to free.</param>
138+ public void FreeExtent ( Extent extent )
139+ {
140+ var offset = extent . StartPage * ( ulong ) _pageSize ;
141+ FreePages ( offset , ( int ) extent . Length ) ;
142+
143+ // ✅ Add to ExtentAllocator for future reuse
144+ lock ( _allocationLock )
145+ {
146+ _extentAllocator . Free ( extent ) ;
147+ _isDirty = true ;
148+ }
149+ }
150+
151+ /// <summary>
152+ /// Gets comprehensive FSM statistics.
153+ /// ✅ SCDB Phase 2: Includes ExtentAllocator metrics.
154+ /// </summary>
155+ /// <returns>Statistics including fragmentation and extent information.</returns>
156+ public FsmStatistics GetDetailedStatistics ( )
157+ {
158+ lock ( _allocationLock )
159+ {
160+ var usedPages = _totalPages - _freePages ;
161+ var largestExtent = _extentAllocator . GetLargestExtentSize ( ) ;
162+ var extentCount = _extentAllocator . ExtentCount ;
163+
164+ // Calculate fragmentation percentage
165+ // Fragmentation = (1 - (largest_extent / free_pages)) * 100
166+ var fragmentation = 0.0 ;
167+ if ( _freePages > 0 )
168+ {
169+ fragmentation = ( 1.0 - ( ( double ) largestExtent / ( double ) _freePages ) ) * 100.0 ;
170+ fragmentation = Math . Max ( 0 , Math . Min ( 100 , fragmentation ) ) ; // Clamp 0-100
171+ }
172+
173+ return new FsmStatistics
174+ {
175+ TotalPages = ( long ) _totalPages ,
176+ FreePages = ( long ) _freePages ,
177+ UsedPages = ( long ) usedPages ,
178+ FreeSpace = ( long ) _freePages * _pageSize ,
179+ LargestExtent = ( long ) largestExtent ,
180+ ExtentCount = extentCount ,
181+ FragmentationPercent = fragmentation
182+ } ;
183+ }
184+ }
185+
58186 public ulong AllocatePages ( int count )
59187 {
60188 ArgumentOutOfRangeException . ThrowIfNegativeOrZero ( count ) ;
@@ -157,7 +285,7 @@ public async Task FlushAsync(CancellationToken cancellationToken = default)
157285
158286 // Calculate L2 extent size
159287 var extentCount = _l2Extents . Count ;
160- var extentSizeBytes = extentCount * FreeExtent . SIZE ;
288+ var extentSizeBytes = extentCount * Scdb . FreeExtent . SIZE ;
161289
162290 // Total size
163291 totalSize = FreeSpaceMapHeader . SIZE + bitmapSizeBytes + sizeof ( int ) + extentSizeBytes ;
@@ -199,7 +327,7 @@ public async Task FlushAsync(CancellationToken cancellationToken = default)
199327 for ( var i = 0 ; i < extentCount ; i ++ )
200328 {
201329 var extent = _l2Extents [ i ] ;
202- var extentSpan = span . Slice ( extentOffset + ( i * FreeExtent . SIZE ) , FreeExtent . SIZE ) ;
330+ var extentSpan = span . Slice ( extentOffset + ( i * Scdb . FreeExtent . SIZE ) , Scdb . FreeExtent . SIZE ) ;
203331 MemoryMarshal . Write ( extentSpan , in extent ) ;
204332 }
205333
@@ -233,6 +361,7 @@ public void Dispose()
233361 }
234362 finally
235363 {
364+ _extentAllocator ? . Dispose ( ) ; // ✅ SCDB Phase 2: Dispose ExtentAllocator
236365 _disposed = true ;
237366 }
238367 }
@@ -366,7 +495,7 @@ private void LoadFsm()
366495 // Read L2 extents
367496 if ( extentCount > 0 )
368497 {
369- var extentBufferSize = extentCount * FreeExtent . SIZE ;
498+ var extentBufferSize = extentCount * Scdb . FreeExtent . SIZE ;
370499 var extentBuffer = ArrayPool < byte > . Shared . Rent ( extentBufferSize ) ;
371500 try
372501 {
@@ -375,8 +504,8 @@ private void LoadFsm()
375504
376505 for ( var i = 0 ; i < extentCount ; i ++ )
377506 {
378- var offset = i * FreeExtent . SIZE ;
379- var extent = MemoryMarshal . Read < FreeExtent > ( extentSpan [ offset ..] ) ;
507+ var offset = i * Scdb . FreeExtent . SIZE ;
508+ var extent = MemoryMarshal . Read < Scdb . FreeExtent > ( extentSpan [ offset ..] ) ;
380509 _l2Extents . Add ( extent ) ;
381510 }
382511 }
0 commit comments