$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]; } } */