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 }