|
$export_common #library "ZMEMORY"
|
|
$export_common #include "zcommon.acs"
|
|
|
|
#define muffins 42
|
|
|
|
$export_common #include "ZMEMORY_g.acs"
|
|
$export_common #include "ZMEMORY_w.acs"
|
|
|
|
//Simple linked list implementation of malloc
|
|
|
|
//$export_common global int 63:gmemory[];
|
|
//$export_common world int 255:wmemory[];
|
|
|
|
$export_option
|
|
global int 63:gmemory[];
|
|
$
|
|
world int 255:wmemory[];
|
|
$
|
|
|
|
$export_common #libdefine nullptr 0
|
|
|
|
$export_common //#define nullptr 0
|
|
|
|
$export_common #define malloc_allocated 0
|
|
$export_common #define malloc_size 1
|
|
$export_common #define malloc_next_header 2
|
|
$export_common #define malloc_prev_header 3
|
|
$export_common #define malloc_num_header_properties 4
|
|
|
|
$export_common #define p_malloc_init_flag_location 0
|
|
$export_common #define p_malloc_first_header_location 1
|
|
|
|
$$export_split
|
|
//[Z] The reason I do it like this is because whoever made the acc is insane.
|
|
#define @nullptr 0
|
|
|
|
#define @malloc_allocated 0
|
|
#define @malloc_size 1
|
|
#define @malloc_next_header 2
|
|
#define @malloc_prev_header 3
|
|
#define @malloc_num_header_properties 4
|
|
|
|
#define @p_malloc_init_flag_location 0
|
|
#define @p_malloc_first_header_location 1
|
|
$$
|
|
|
|
$$export_split function int @malloc (int size) {
|
|
//Do the setup on the first run of this function.
|
|
if(@memory[@p_malloc_init_flag_location] == FALSE) { //Default values for global values is 0, so this is true.
|
|
@memory[@p_malloc_init_flag_location] = TRUE;
|
|
@memory[@p_malloc_first_header_location+@malloc_allocated] = FALSE;
|
|
@memory[@p_malloc_first_header_location+@malloc_size] = -1; //"infinite"
|
|
@memory[@p_malloc_first_header_location+@malloc_next_header] = @nullptr; //nullptr
|
|
@memory[@p_malloc_first_header_location+@malloc_prev_header] = @nullptr; //nullptr
|
|
}
|
|
|
|
int p_previous_header = @nullptr;
|
|
int p_current_header = @p_malloc_first_header_location;
|
|
int p_retval = @nullptr;
|
|
|
|
while(p_retval == @nullptr) {
|
|
int memalloced = @memory[p_current_header+@malloc_allocated];
|
|
int memsize = @memory[p_current_header+@malloc_size];
|
|
if(memsize == -1) { //The end of the list.
|
|
@memory[p_current_header+@malloc_allocated] = TRUE;
|
|
@memory[p_current_header+@malloc_size] = size;
|
|
@memory[p_current_header+@malloc_next_header] = p_current_header+@malloc_num_header_properties+size; //New EOL
|
|
@memory[p_current_header+@malloc_prev_header] = p_previous_header;
|
|
|
|
//Retrieve the return value while we are at the allocated space.
|
|
p_retval = p_current_header+@malloc_num_header_properties;
|
|
|
|
//Remember to initialize the new end list node.
|
|
p_previous_header = p_current_header; //This is the header previous to the EOL.
|
|
p_current_header = @memory[p_current_header+@malloc_next_header];
|
|
//Set the tail node constants.
|
|
@memory[p_current_header+@malloc_allocated] = FALSE;
|
|
@memory[p_current_header+@malloc_size] = -1;
|
|
@memory[p_current_header+@malloc_next_header] = @nullptr;
|
|
@memory[p_current_header+@malloc_prev_header] = p_previous_header;
|
|
} else if(memsize >= size && memalloced == FALSE) { //There is room here AND it isn't in use,
|
|
@memory[p_current_header+@malloc_allocated] = TRUE;
|
|
//The size isn't modified because we are re-using an existing space.
|
|
// It would be a good idea to check just how large this space is and act accordingly rather
|
|
// than using a 500 indexes large space for a 4 indexes large object.
|
|
// Objects allocated in a doom mod probably won't be outside the 1-16 indexes range so it
|
|
// should still be fine for most applications.
|
|
//The next header isn't changed either for the same reason.
|
|
if(memsize >= (size+@malloc_num_header_properties+5)) { //Assume that 5 is the smallest useful allocation size.
|
|
int p_split_newheader = p_current_header+@malloc_num_header_properties+size; //Just to the end of the allocation.
|
|
@memory[p_split_newheader+@malloc_allocated] = FALSE;
|
|
@memory[p_split_newheader+@malloc_size] = @memory[p_current_header+@malloc_size]-@malloc_num_header_properties-size;
|
|
@memory[p_split_newheader+@malloc_next_header] = @memory[p_current_header+@malloc_next_header];
|
|
@memory[p_split_newheader+@malloc_prev_header] = p_current_header;
|
|
@memory[p_current_header+@malloc_next_header] = p_split_newheader; //The header whose block was split should have its next pointer set to its other half.
|
|
@memory[p_current_header+@malloc_size] = size; //Set the size of the allocation to reflect the split.
|
|
}
|
|
|
|
//Retrieve the return value while we are at the allocated space.
|
|
p_retval = p_current_header+@malloc_num_header_properties;
|
|
} else {
|
|
//The observed node isn't useful for allocating the request. Go to the next node.
|
|
p_previous_header = p_current_header;
|
|
p_current_header = @memory[p_current_header+@malloc_next_header];
|
|
}
|
|
}
|
|
|
|
//log(s:"Malloc allocated ", d:size, s:" indexes at location ", d:p_retval, s:" with header location ", d:p_current_header);
|
|
|
|
return p_retval;
|
|
}
|
|
$$
|
|
|
|
$$export_split function int @free (int p_ptr) {
|
|
log(s:"Invoked free");
|
|
|
|
int p_header = p_ptr - @malloc_num_header_properties;
|
|
|
|
@memory[p_header+@malloc_allocated] = FALSE;
|
|
|
|
int p_next = @memory[p_header+@malloc_next_header];
|
|
int p_prev = @memory[p_header+@malloc_prev_header];
|
|
|
|
//Below is the merging of free blocks.
|
|
//It merges to the left (lower indexes) first becaue the right (larger
|
|
// indexes) has a special case. (the end of the list)
|
|
|
|
//the previous block is unused. Merge.
|
|
if(p_prev != @nullptr //This doesn't make sense if the previous block doesn't exist.
|
|
&& @memory[p_prev+@malloc_allocated] == FALSE) {
|
|
log(s:"Free attempting merge of header ", d:p_header, s: " to the left with header ", d:p_prev);
|
|
@memory[p_prev+@malloc_size] += @memory[p_header+@malloc_size] + @malloc_num_header_properties;
|
|
@memory[p_prev+@malloc_next_header] = p_next; //The prev header needs to know the new next.
|
|
@memory[p_next+@malloc_prev_header] = p_prev; //The next header needs to know the new prev.
|
|
|
|
//Now the two blocks are the same block. requires new initializations of the
|
|
// variables for the other check to function correctly.
|
|
//The header is the result of the merge, and the prev is the one before it.
|
|
p_header = p_prev;
|
|
p_prev = @memory[p_header+@malloc_prev_header];
|
|
}
|
|
|
|
//The next block is unused. Merge.
|
|
//Note that p_next will never be a nullptr with correct usage.
|
|
if(@memory[p_next+@malloc_allocated] == FALSE) {
|
|
if(@memory[p_next+@malloc_size] == -1) { //EOL
|
|
@memory[p_header+@malloc_size] = -1;
|
|
@memory[p_header+@malloc_next_header] = @nullptr;
|
|
} else {
|
|
int p_next_next = @memory[p_next+@malloc_next_header];
|
|
@memory[p_header+@malloc_size] += @memory[p_next+@malloc_size] + @malloc_num_header_properties;
|
|
@memory[p_header+@malloc_next_header] = p_next_next; //The header on the other side of the next header needs to know its new prev.
|
|
@memory[p_next_next+@malloc_prev_header] = p_header; //This header needs to know its new next.
|
|
}
|
|
}
|
|
|
|
|
|
return -1;
|
|
}
|
|
$$
|
|
|
|
|
|
//These are for debugging
|
|
/*
|
|
script "memory_write" (int index, int value) {
|
|
memory[index] = value;
|
|
}
|
|
|
|
script "memory_read" (int index) {
|
|
log(d:memory[index]);
|
|
}
|
|
|
|
script "memory_print_allocation_list" (void) {
|
|
int p_current_header = p_malloc_first_header_location;
|
|
|
|
while(p_current_header != nullptr) {
|
|
log(s: "Header location: ", d:p_current_header,
|
|
s:"\n Allocated flag: ", d:memory[p_current_header+malloc_allocated],
|
|
s:"\n Allocation size: ", d:memory[p_current_header+malloc_size],
|
|
s:"\n Prev header pointer: ", d:memory[p_current_header+malloc_prev_header],
|
|
s:"\n Next header pointer: ", d:memory[p_current_header+malloc_next_header],
|
|
s:"\n"
|
|
);
|
|
|
|
p_current_header = memory[p_current_header+malloc_next_header];
|
|
}
|
|
}
|
|
*/
|
|
|