1 /** 2 Memory Pools 3 4 Copyright: 5 Copyright © 2020-2026, Inochi2D Project 6 7 License: 8 $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License) 9 10 Authors: 11 Luna Nielsen 12 */ 13 module inochi2d.core.memory.pool; 14 import inochi2d; 15 import numem.core.hooks; 16 import numem.core.math; 17 import numem; 18 19 /** 20 A frame pool. 21 22 Frame pools allow reuse of memory that changes frequently, eg. every frame. 23 24 Frame pools are grown by a given "page" size if additions to the pool go beyond 25 the current allocation of the pool. 26 */ 27 struct FramePool { 28 private: 29 @nogc: 30 void[] memory_; 31 size_t pageSize_ = 4096; 32 size_t generation_ = 0; 33 size_t i = 0; 34 35 public: 36 37 /** 38 The current generation of the frame pool. 39 */ 40 @property size_t generation() => generation_; 41 42 /// Destructor 43 ~this() { 44 this.free(); 45 } 46 47 /** 48 Initializes a new memory pool. 49 50 Params: 51 pageSize = The amount of bytes the pool grows by. (minimum 4096) 52 */ 53 this(size_t pageSize) @trusted nothrow { 54 this.pageSize_ = nu_min(4096, pageSize); 55 this.memory_ = nu_malloc(pageSize)[0..pageSize]; 56 } 57 58 /** 59 Begins the next frame/generation. 60 61 Note: 62 This will render all current pool tokens invalid. 63 */ 64 void next() { 65 this.i = 0; 66 this.generation_++; 67 } 68 69 /** 70 Frees the pool. 71 72 Note: 73 This makes the pool invalid, if you desire a fresh 74 pool instantiate a new one with the constructor. 75 */ 76 void free() { 77 nu_free(memory_.ptr); 78 this.memory_ = null; 79 this.generation_ = size_t.max; 80 } 81 82 /** 83 Allocates memory from the pool for the given amount of elements. 84 85 Params: 86 count = How many elements to allocate from the pool. 87 88 Returns: 89 A slice of the allocated memory. 90 */ 91 PoolToken!T allocate(T)(size_t count) @trusted nothrow { 92 if (count == 0) 93 return typeof(return).init; 94 95 size_t si = i; 96 size_t toalloc = (T.sizeof*count); 97 98 // Grow memory if needed. 99 if (i + toalloc > memory_.length) { 100 size_t newsz = nu_alignup(memory_.length+toalloc, pageSize_); 101 this.memory_ = nu_realloc(memory_.ptr, newsz)[0..newsz]; 102 } 103 104 i += toalloc; 105 return PoolToken!T( 106 pool: &this, 107 generation_: generation_, 108 offset: si, 109 length: toalloc 110 ); 111 } 112 } 113 114 /** 115 A token that represents an allocation within a frame pool. 116 */ 117 struct PoolToken(T) { 118 private: 119 @nogc: 120 FramePool* pool; 121 size_t generation_; 122 size_t offset; 123 size_t length; 124 125 public: 126 alias value this; 127 128 /** 129 Whether the token is still valid. 130 */ 131 @property bool isValid() @trusted nothrow pure => (pool !is null) && (this.generation_ == pool.generation_); 132 133 /** 134 The value of the token. 135 */ 136 @property T[] value() @trusted nothrow { 137 return isValid ? 138 cast(T[])pool.memory_[offset..offset+length] : 139 null; 140 } 141 } 142 143 @("FramePool: allocate") 144 unittest { 145 FramePool pool = FramePool(4096); 146 auto token = pool.allocate!float(32); 147 148 token[0..32] = 100f; 149 foreach(i; 0..32) 150 assert(token[i] == 100f); 151 } 152 153 @("FramePool: generations") 154 unittest { 155 FramePool pool = FramePool(4096); 156 auto token = pool.allocate!float(32); 157 pool.next(); 158 assert(!token.isValid); 159 }