rewrote memory system entirely (hunk, cache, and zone are gone, memory pools replaced...
[xonotic/darkplaces.git] / zone.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // Z_zone.c
21
22 #include "quakedef.h"
23
24 mempool_t *poolchain = NULL;
25
26 void *_Mem_Alloc(mempool_t *pool, int size, char *filename, int fileline)
27 {
28         int i, j, k, needed, endbit, largest;
29         memclump_t *clump, **clumpchainpointer;
30         memheader_t *mem;
31         if (size <= 0)
32                 return NULL;
33         if (pool == NULL)
34                 Host_Error("Mem_Alloc: pool == NULL");
35         pool->totalsize += size;
36         if (size < 4096)
37         {
38                 // clumping
39                 needed = (sizeof(memheader_t) + size + sizeof(int) + (MEMUNIT - 1)) / MEMUNIT;
40                 endbit = MEMBITS - needed;
41                 for (clumpchainpointer = &pool->clumpchain;*clumpchainpointer;clumpchainpointer = &(*clumpchainpointer)->chain)
42                 {
43                         clump = *clumpchainpointer;
44                         if (clump->sentinel1 != MEMCLUMP_SENTINEL)
45                                 Sys_Error("Mem_Alloc: trashed clump sentinel 1\n");
46                         if (clump->sentinel2 != MEMCLUMP_SENTINEL)
47                                 Sys_Error("Mem_Alloc: trashed clump sentinel 2\n");
48                         if (clump->largestavailable >= needed)
49                         {
50                                 largest = 0;
51                                 for (i = 0;i < endbit;i++)
52                                 {
53                                         if (clump->bits[i >> 5] & (1 << (i & 31)))
54                                                 continue;
55                                         k = i + needed;
56                                         for (j = i;i < k;i++)
57                                                 if (clump->bits[i >> 5] & (1 << (i & 31)))
58                                                         goto loopcontinue;
59                                         goto choseclump;
60         loopcontinue:;
61                                         if (largest < j - i)
62                                                 largest = j - i;
63                                 }
64                                 // since clump falsely advertised enough space (nothing wrong
65                                 // with that), update largest count to avoid wasting time in
66                                 // later allocations
67                                 clump->largestavailable = largest;
68                         }
69                 }
70                 pool->realsize += sizeof(memclump_t);
71                 clump = malloc(sizeof(memclump_t));
72                 if (clump == NULL)
73                         Host_Error("Mem_Alloc: out of memory");
74                 memset(clump, 0, sizeof(memclump_t));
75                 *clumpchainpointer = clump;
76                 clump->sentinel1 = MEMCLUMP_SENTINEL;
77                 clump->sentinel2 = MEMCLUMP_SENTINEL;
78                 clump->chain = NULL;
79                 clump->blocksinuse = 0;
80                 clump->largestavailable = MEMBITS - needed;
81                 j = 0;
82 choseclump:
83                 mem = (memheader_t *)((long) clump->block + j * MEMUNIT);
84                 mem->clump = clump;
85                 clump->blocksinuse += needed;
86                 for (i = j + needed;j < i;j++)
87                         clump->bits[j >> 5] |= (1 << (j & 31));
88         }
89         else
90         {
91                 // big allocations are not clumped
92                 pool->realsize += sizeof(memheader_t) + size + sizeof(int);
93                 mem = malloc(sizeof(memheader_t) + size + sizeof(int));
94                 if (mem == NULL)
95                         Host_Error("Mem_Alloc: out of memory");
96                 mem->clump = NULL;
97         }
98         mem->filename = filename;
99         mem->fileline = fileline;
100         mem->size = size;
101         mem->pool = pool;
102         mem->sentinel1 = MEMHEADER_SENTINEL;
103         *((int *)((long) mem + sizeof(memheader_t) + mem->size)) = MEMHEADER_SENTINEL;
104         // append to head of list
105         mem->chain = pool->chain;
106         pool->chain = mem;
107         memset((void *)((long) mem + sizeof(memheader_t)), 0, mem->size);
108         return (void *)((long) mem + sizeof(memheader_t));
109 }
110
111 void Mem_Free(void *data)
112 {
113         int i, firstblock, endblock;
114         memclump_t *clump, **clumpchainpointer;
115         memheader_t *mem, **memchainpointer;
116         mempool_t *pool;
117         if (data == NULL)
118                 Host_Error("Mem_Free: data == NULL");
119
120
121         mem = (memheader_t *)((long) data - sizeof(memheader_t));
122         if (mem->sentinel1 != MEMHEADER_SENTINEL)
123                 Sys_Error("Mem_Free: trashed header sentinel 1 (block allocated in %s:%i)\n", mem->filename, mem->fileline);
124         if (*((int *)((long) mem + sizeof(memheader_t) + mem->size)) != MEMHEADER_SENTINEL)
125                 Sys_Error("Mem_Free: trashed header sentinel 2 (block allocated in %s:%i)\n", mem->filename, mem->fileline);
126         pool = mem->pool;
127         for (memchainpointer = &pool->chain;*memchainpointer;memchainpointer = &(*memchainpointer)->chain)
128         {
129                 if (*memchainpointer == mem)
130                 {
131                         *memchainpointer = mem->chain;
132                         pool->totalsize -= mem->size;
133                         if ((clump = mem->clump))
134                         {
135                                 if (clump->sentinel1 != MEMCLUMP_SENTINEL)
136                                         Sys_Error("Mem_Alloc: trashed clump sentinel 1\n");
137                                 if (clump->sentinel2 != MEMCLUMP_SENTINEL)
138                                         Sys_Error("Mem_Alloc: trashed clump sentinel 2\n");
139                                 firstblock = ((long) mem - (long) clump->block);
140                                 if (firstblock & (MEMUNIT - 1))
141                                         Host_Error("Mem_Free: address not valid in clump\n");
142                                 firstblock /= MEMUNIT;
143                                 endblock = firstblock + ((sizeof(memheader_t) + mem->size + sizeof(int) + (MEMUNIT - 1)) / MEMUNIT);
144                                 clump->blocksinuse -= endblock - firstblock;
145                                 // could use &, but we know the bit is set
146                                 for (i = firstblock;i < endblock;i++)
147                                         clump->bits[i >> 5] -= (1 << (i & 31));
148                                 if (clump->blocksinuse <= 0)
149                                 {
150                                         // unlink from chain
151                                         for (clumpchainpointer = &pool->clumpchain;*clumpchainpointer;clumpchainpointer = &(*clumpchainpointer)->chain)
152                                         {
153                                                 if (*clumpchainpointer == clump)
154                                                 {
155                                                         *clumpchainpointer = clump->chain;
156                                                         break;
157                                                 }
158                                         }
159                                         pool->realsize -= sizeof(memclump_t);
160                                         memset(clump, 0xBF, sizeof(memclump_t));
161                                         free(clump);
162                                 }
163                                 else
164                                 {
165                                         // clump still has some allocations
166                                         // force re-check of largest available space on next alloc
167                                         clump->largestavailable = MEMBITS - clump->blocksinuse;
168                                 }
169                         }
170                         else
171                         {
172                                 pool->realsize -= sizeof(memheader_t) + mem->size + sizeof(int);
173                                 memset(mem, 0xBF, sizeof(memheader_t) + mem->size + sizeof(int));
174                                 free(mem);
175                         }
176                         return;
177                 }
178         }
179         Host_Error("Mem_Free: not allocated\n");
180 }
181
182 mempool_t *Mem_AllocPool(char *name)
183 {
184 //      int i;
185         mempool_t *pool;
186         pool = malloc(sizeof(mempool_t));
187         if (pool == NULL)
188                 Host_Error("Mem_AllocPool: out of memory");
189         memset(pool, 0, sizeof(mempool_t));
190         pool->chain = NULL;
191         pool->totalsize = 0;
192         pool->realsize = sizeof(mempool_t);
193         strcpy(pool->name, name);
194 //      for (i = 0;i < (POOLNAMESIZE - 1) && name[i];i++)
195 //              pool->name[i] = name[i];
196 //      for (i = 0;i < POOLNAMESIZE;i++)
197 //              pool->name[i] = 0;
198         pool->next = poolchain;
199         poolchain = pool;
200         return pool;
201 }
202
203 void Mem_FreePool(mempool_t **pool)
204 {
205         mempool_t **chainaddress;
206         if (*pool)
207         {
208                 // unlink pool from chain
209                 for (chainaddress = &poolchain;*chainaddress && *chainaddress != *pool;chainaddress = &((*chainaddress)->next));
210                 if (*chainaddress != *pool)
211                         Host_Error("Mem_FreePool: pool already free");
212                 *chainaddress = (*pool)->next;
213
214                 // free memory owned by the pool
215                 while ((*pool)->chain)
216                         Mem_Free((void *)((long) (*pool)->chain + sizeof(memheader_t)));
217
218                 // free the pool itself
219                 memset(*pool, 0xBF, sizeof(mempool_t));
220                 free(*pool);
221                 *pool = NULL;
222         }
223 }
224
225 void Mem_EmptyPool(mempool_t *pool)
226 {
227         if (pool == NULL)
228                 Con_Printf("Mem_EmptyPool: pool == NULL\n");
229
230         // free memory owned by the pool
231         while (pool->chain)
232                 Mem_Free((void *)((long) pool->chain + sizeof(memheader_t)));
233 }
234
235 void _Mem_CheckSentinels(void *data, char *filename, int fileline)
236 {
237         memheader_t *mem;
238
239         if (data == NULL)
240                 Host_Error("Mem_CheckSentinels: data == NULL\n");
241
242         mem = (memheader_t *)((long) data - sizeof(memheader_t));
243         if (mem->sentinel1 != MEMHEADER_SENTINEL)
244                 Host_Error("Mem_CheckSentinels: trashed header sentinel 1 (block allocated at %s:%i, sentinel check at %s:%i)\n", mem->filename, mem->fileline, filename, fileline);
245         if (*((int *)((long) mem + sizeof(memheader_t) + mem->size)) != MEMHEADER_SENTINEL)
246                 Host_Error("Mem_CheckSentinels: trashed header sentinel 2 (block allocated at %s:%i, sentinel check at %s:%i)\n", mem->filename, mem->fileline, filename, fileline);
247 }
248
249 static void _Mem_CheckClumpSentinels(memclump_t *clump, char *filename, int fileline)
250 {
251         // this isn't really very useful
252         if (clump->sentinel1 != MEMCLUMP_SENTINEL)
253                 Host_Error("Mem_CheckClumpSentinels: trashed sentinel 1 (sentinel check at %s:%i)\n", filename, fileline);
254         if (clump->sentinel2 != MEMCLUMP_SENTINEL)
255                 Host_Error("Mem_CheckClumpSentinels: trashed sentinel 2 (sentinel check at %s:%i)\n", filename, fileline);
256 }
257
258 void _Mem_CheckSentinelsGlobal(char *filename, int fileline)
259 {
260         memheader_t *mem;
261         memclump_t *clump;
262         mempool_t *pool;
263         for (pool = poolchain;pool;pool = pool->next)
264         {
265                 for (mem = pool->chain;mem;mem = mem->chain)
266                         _Mem_CheckSentinels((void *)((long) mem + sizeof(memheader_t)), filename, fileline);
267                 for (clump = pool->clumpchain;clump;clump = clump->chain)
268                         _Mem_CheckClumpSentinels(clump, filename, fileline);
269         }
270 }
271
272 // used for temporary memory allocations around the engine, not for longterm storage
273 mempool_t *tempmempool;
274 // only for zone
275 mempool_t *zonemempool;
276
277 void Mem_PrintStats(void)
278 {
279         int count = 0, size = 0;
280         mempool_t *pool;
281         for (pool = poolchain;pool;pool = pool->next)
282         {
283                 count++;
284                 size += pool->totalsize;
285         }
286         Con_Printf("%i memory pools, totalling %i bytes (%.3fMB)\n", count, size, size / 1048576.0);
287         if (tempmempool == NULL)
288                 Con_Printf("Error: no tempmempool allocated\n");
289         else if (tempmempool->chain)
290                 Con_Printf("%i bytes (%.3fMB) of temporary memory still allocated (Leak!)\n", tempmempool->totalsize, tempmempool->totalsize / 1048576.0);
291 }
292
293 void Mem_PrintList_f(void)
294 {
295         mempool_t *pool;
296         Con_Printf("memory pool list:\n"
297                    "size    name\n");
298         for (pool = poolchain;pool;pool = pool->next)
299         {
300                 if (pool->lastchecksize != 0 && pool->totalsize != pool->lastchecksize)
301                         Con_Printf("%6ik (%6ik actual) %s (%i byte change)\n", (pool->totalsize + 1023) / 1024, (pool->realsize + 1023) / 1024, pool->name, pool->totalsize - pool->lastchecksize);
302                 else
303                         Con_Printf("%6ik (%6ik actual) %s\n", (pool->totalsize + 1023) / 1024, (pool->realsize + 1023) / 1024, pool->name);
304                 pool->lastchecksize = pool->totalsize;
305         }
306         Mem_PrintStats();
307 }
308
309 extern void R_TextureStats_PrintTotal(void);
310 void Memstats_f(void)
311 {
312         R_TextureStats_PrintTotal();
313         Mem_PrintStats();
314 }
315
316
317 /*
318 ========================
319 Memory_Init
320 ========================
321 */
322 void Memory_Init (void)
323 {
324         tempmempool = Mem_AllocPool("Temporary Memory");
325         zonemempool = Mem_AllocPool("Zone");
326 }
327
328 void Memory_Init_Commands (void)
329 {
330         Cmd_AddCommand ("memstats", Memstats_f);
331         Cmd_AddCommand ("memlist", Mem_PrintList_f);
332 }
333