
IF YOU HAVE A WORKING AND TESTED BUGFIX PUT IT INTO STABLE AS WELL AS TRUNK. EVERYTHING ELSE GOES INTO TRUNK AND WILL BE MERGED INTO STABLE BY VALARIS AND WIZPUTER. -- VALARIS git-svn-id: https://svn.code.sf.net/p/rathena/svn/trunk@5094 54d463be-8e91-2dee-dedb-b68131a5f0ec
533 lines
17 KiB
C
533 lines
17 KiB
C
/*****************************************************************************\
|
|
* Copyright (c) Athena Dev Teams - Licensed under GNU GPL *
|
|
* For more information, see LICENCE in the main folder *
|
|
* *
|
|
* <H1>Entry Reusage System</H1> *
|
|
* *
|
|
* There are several root entry managers, each with a different entry size. *
|
|
* Each manager will keep track of how many instances have been 'created'. *
|
|
* They will only automatically destroy themselves after the last instance *
|
|
* is destroyed. *
|
|
* *
|
|
* Entries can be allocated from the managers. *
|
|
* If it has reusable entries (freed entry), it uses one. *
|
|
* So no assumption should be made about the data of the entry. *
|
|
* Entries should be freed in the manager they where allocated from. *
|
|
* Failure to do so can lead to unexpected behaviours. *
|
|
* *
|
|
* <H2>Advantages:</H2> *
|
|
* - The same manager is used for entries of the same size. *
|
|
* So entries freed in one instance of the manager can be used by other *
|
|
* instances of the manager. *
|
|
* - Much less memory allocation/deallocation - program will be faster. *
|
|
* - Avoids memory fragmentaion - program will run better for longer. *
|
|
* *
|
|
* <H2>Disavantages:</H2> *
|
|
* - Unused entries are almost inevitable - memory being wasted. *
|
|
* - A manager will only auto-destroy when all of its instances are *
|
|
* destroyed so memory will usually only be recovered near the end. *
|
|
* - Always wastes space for entries smaller than a pointer. *
|
|
* *
|
|
* WARNING: The system is not thread-safe at the moment. *
|
|
* *
|
|
* HISTORY: *
|
|
* 0.1 - Initial version *
|
|
* *
|
|
* @version 0.1 - Initial version *
|
|
* @author Flavio @ Amazon Project *
|
|
* @encoding US-ASCII *
|
|
* @see common#ers.h *
|
|
\*****************************************************************************/
|
|
#include <stdlib.h>
|
|
|
|
#include "ers.h"
|
|
#include "../common/malloc.h" // CREATE, RECREATE, aMalloc, aFree
|
|
#include "../common/showmsg.h" // ShowMessage, ShowError, ShowFatalError, CL_BOLD, CL_NORMAL
|
|
|
|
#ifndef DISABLE_ERS
|
|
/*****************************************************************************\
|
|
* (1) Private defines, structures and global variables. *
|
|
* ERS_BLOCK_ENTRIES - Number of entries in each block. *
|
|
* ERS_ROOT_SIZE - Maximum number of root entry managers. *
|
|
* ERLinkedList - Structure of a linked list of reusable entries. *
|
|
* ERSystem - Class of an entry manager. *
|
|
* ers_root - Array of root entry managers. *
|
|
* ers_num - Number of root entry managers in the array. *
|
|
\*****************************************************************************/
|
|
|
|
/**
|
|
* Number of entries in each block.
|
|
* @private
|
|
* @see #ers_obj_alloc_entry(ERInterface eri)
|
|
*/
|
|
#define ERS_BLOCK_ENTRIES 4096
|
|
|
|
/**
|
|
* Maximum number of root entry managers.
|
|
* @private
|
|
* @see #ers_root
|
|
* @see #ers_num
|
|
*/
|
|
#define ERS_ROOT_SIZE 256
|
|
|
|
/**
|
|
* Linked list of reusable entries.
|
|
* The minimum size of the entries is the size of this structure.
|
|
* @private
|
|
* @see ERSystem#reuse
|
|
*/
|
|
typedef struct ers_ll {
|
|
struct ers_ll *next;
|
|
} *ERLinkedList;
|
|
|
|
/**
|
|
* Class of the object that manages entries of a certain size.
|
|
* @param eri Public interface of the object
|
|
* @param reuse Linked list of reusable data entries
|
|
* @param blocks Array with blocks of entries
|
|
* @param free Number of unused entries in the last block
|
|
* @param num Number of blocks in the array
|
|
* @param max Current maximum capacity of the array
|
|
* @param destroy Destroy lock
|
|
* @param size Size of the entries of the manager
|
|
* @private
|
|
*/
|
|
typedef struct ers {
|
|
|
|
/**
|
|
* Public interface of the entry manager.
|
|
* @param alloc Allocate an entry from this manager
|
|
* @param free Free an entry allocated from this manager
|
|
* @param entry_size Return the size of the entries of this manager
|
|
* @param destroy Destroy this instance of the manager
|
|
* @public
|
|
* @see #ERSystem
|
|
* @see common\ers.h#ERInterface
|
|
*/
|
|
struct eri eri;
|
|
|
|
/**
|
|
* Linked list of reusable entries.
|
|
* @private
|
|
* @see #ERSystem
|
|
*/
|
|
ERLinkedList reuse;
|
|
|
|
/**
|
|
* Array with blocks of entries.
|
|
* @private
|
|
* @see #ERSystem
|
|
*/
|
|
uint8 **blocks;
|
|
|
|
/**
|
|
* Number of unused entries in the last block.
|
|
* @private
|
|
* @see #ERSystem
|
|
*/
|
|
uint32 free;
|
|
|
|
/**
|
|
* Number of blocks in the array.
|
|
* @private
|
|
* @see #ERSystem
|
|
*/
|
|
uint32 num;
|
|
|
|
/**
|
|
* Current maximum capacity of the array.
|
|
* @private
|
|
* @see #ERSystem
|
|
*/
|
|
uint32 max;
|
|
|
|
/**
|
|
* Destroy lock.
|
|
* @private
|
|
* @see #ERSystem
|
|
*/
|
|
uint32 destroy;
|
|
|
|
/**
|
|
* Size of the entries of the manager.
|
|
* @private
|
|
* @see #ERSystem
|
|
*/
|
|
uint32 size;
|
|
|
|
} *ERSystem;
|
|
|
|
/**
|
|
* Root array with entry managers.
|
|
* @private
|
|
* @static
|
|
* @see #ERS_ROOT_SIZE
|
|
* @see #ers_num
|
|
*/
|
|
static ERSystem ers_root[ERS_ROOT_SIZE];
|
|
|
|
/**
|
|
* Number of entry managers in the root array.
|
|
* @private
|
|
* @static
|
|
* @see #ERS_ROOT_SIZE
|
|
* @see #ers_root
|
|
*/
|
|
static uint32 ers_num = 0;
|
|
|
|
/*****************************************************************************\
|
|
* (2) Protected functions. *
|
|
* ers_obj_alloc_entry - Allocate an entry from the manager. *
|
|
* ers_obj_free_entry - Free an entry allocated from the manager. *
|
|
* ers_obj_entry_size - Return the size of the entries of the manager. *
|
|
* ers_obj_destroy - Destroy the instance of the manager. *
|
|
\*****************************************************************************/
|
|
|
|
/**
|
|
* Allocate an entry from this entry manager.
|
|
* If there are reusable entries available, it reuses one instead.
|
|
* @param self Interface of the entry manager
|
|
* @return An entry
|
|
* @protected
|
|
* @see #ERS_BLOCK_ENTRIES
|
|
* @see #ERLinkedList
|
|
* @see #ERSystem
|
|
* @see common\ers.h\ERInterface#alloc(ERInterface)
|
|
*/
|
|
static void *ers_obj_alloc_entry(ERInterface self)
|
|
{
|
|
ERSystem obj = (ERSystem)self;
|
|
void *ret;
|
|
|
|
if (obj == NULL) {
|
|
ShowError("ers_obj_alloc_entry: NULL object, aborting entry allocation.\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (obj->reuse) { // Reusable entry
|
|
ret = obj->reuse;
|
|
obj->reuse = obj->reuse->next;
|
|
} else if (obj->free) { // Unused entry
|
|
obj->free--;
|
|
ret = &obj->blocks[obj->num -1][obj->free*obj->size];
|
|
} else { // allocate a new block
|
|
if (obj->num == obj->max) { // expand the block array
|
|
if (obj->max == UINT32_MAX) { // No more space for blocks
|
|
ShowFatalError("ers_obj_alloc_entry: maximum number of blocks reached, increase ERS_BLOCK_ENTRIES.\n"
|
|
"exiting the program...\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
obj->max = (obj->max<<2) +3; // = obj->max*4 +3; - overflow won't happen
|
|
RECREATE(obj->blocks, uint8 *, obj->max);
|
|
}
|
|
CREATE(obj->blocks[obj->num], uint8, obj->size*ERS_BLOCK_ENTRIES);
|
|
obj->free = ERS_BLOCK_ENTRIES -1;
|
|
ret = &obj->blocks[obj->num][obj->free*obj->size];
|
|
obj->num++;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Free an entry allocated from this manager.
|
|
* WARNING: Does not check if the entry was allocated by this manager.
|
|
* Freeing such an entry can lead to unexpected behaviour.
|
|
* @param self Interface of the entry manager
|
|
* @param entry Entry to be freed
|
|
* @protected
|
|
* @see #ERLinkedList
|
|
* @see #ERSystem
|
|
* @see ERSystem#reuse
|
|
* @see common\ers.h\ERInterface#free(ERInterface,void *)
|
|
*/
|
|
static void ers_obj_free_entry(ERInterface self, void *entry)
|
|
{
|
|
ERSystem obj = (ERSystem)self;
|
|
ERLinkedList reuse;
|
|
|
|
if (obj == NULL) {
|
|
ShowError("ers_obj_free_entry: NULL object, aborting entry freeing.\n");
|
|
return;
|
|
} else if (entry == NULL) {
|
|
ShowError("ers_obj_free_entry: NULL entry, nothing to free.\n");
|
|
return;
|
|
}
|
|
|
|
reuse = (ERLinkedList)entry;
|
|
reuse->next = obj->reuse;
|
|
obj->reuse = reuse;
|
|
}
|
|
|
|
/**
|
|
* Return the size of the entries allocated from this manager.
|
|
* @param self Interface of the entry manager
|
|
* @return Size of the entries of this manager in bytes
|
|
* @protected
|
|
* @see #ERSystem
|
|
* @see ERSystem#size
|
|
* @see common\ers.h\ERInterface#enty_size(ERInterface)
|
|
*/
|
|
static uint32 ers_obj_entry_size(ERInterface self)
|
|
{
|
|
ERSystem obj = (ERSystem)self;
|
|
|
|
if (obj == NULL) {
|
|
ShowError("ers_obj_entry_size: NULL object, returning 0.\n");
|
|
return 0;
|
|
}
|
|
|
|
return obj->size;
|
|
}
|
|
|
|
/**
|
|
* Destroy this instance of the manager.
|
|
* The manager is actually only destroyed when all the instances are destroyed.
|
|
* When destroying the manager a warning is shown if the manager has
|
|
* missing/extra entries.
|
|
* @param self Interface of the entry manager
|
|
* @protected
|
|
* @see #ERLinkedList
|
|
* @see #ERSystem
|
|
* @see common\ers.h\ERInterface#destroy(ERInterface)
|
|
*/
|
|
static void ers_obj_destroy(ERInterface self)
|
|
{
|
|
ERSystem obj = (ERSystem)self;
|
|
ERLinkedList reuse;
|
|
uint32 i, count;
|
|
|
|
if (obj == NULL) {
|
|
ShowError("ers_obj_destroy: NULL object, aborting instance destruction.\n");
|
|
return;
|
|
}
|
|
|
|
obj->destroy--;
|
|
if (obj->destroy)
|
|
return; // Not last instance
|
|
|
|
// Remove manager from root array
|
|
for (i = 0; i < ers_num; i++) {
|
|
if (ers_root[i] == obj) {
|
|
ers_num--;
|
|
if (i < ers_num) // put the last manager in the free slot
|
|
ers_root[i] = ers_root[ers_num];
|
|
break;
|
|
}
|
|
}
|
|
reuse = obj->reuse;
|
|
count = 0;
|
|
// Check for missing/extra entries
|
|
for (i = 0; i < obj->num; i++) {
|
|
if (i == 0) {
|
|
count = ERS_BLOCK_ENTRIES -obj->free;
|
|
} else if (count > UINT32_MAX -ERS_BLOCK_ENTRIES) {
|
|
count = UINT32_MAX;
|
|
break;
|
|
} else {
|
|
count += ERS_BLOCK_ENTRIES;
|
|
}
|
|
while (reuse && count) {
|
|
count--;
|
|
reuse = reuse->next;
|
|
}
|
|
}
|
|
if (count) { // missing entries
|
|
ShowWarning("ers_obj_destroy: %u entries missing, continuing destruction.\n"
|
|
"Manager for entries of size %u.\n",
|
|
count, obj->size);
|
|
} else if (reuse) { // extra entries
|
|
while (reuse && count != UINT32_MAX) {
|
|
count++;
|
|
reuse = reuse->next;
|
|
}
|
|
ShowWarning("ers_obj_destroy: %u extra entries found, continuing destruction.\n"
|
|
"Manager for entries of size %u.\n",
|
|
count, obj->size);
|
|
}
|
|
// destroy the entry manager
|
|
if (obj->max) {
|
|
for (i = 0; i < obj->num; i++)
|
|
aFree(obj->blocks[i]); // release block of entries
|
|
aFree(obj->blocks); // release array of blocks
|
|
}
|
|
aFree(obj); // release manager
|
|
}
|
|
|
|
/*****************************************************************************\
|
|
* (3) Public functions. *
|
|
* ers_new - Get a new instance of an entry manager. *
|
|
* ers_report - Print a report about the current state. *
|
|
* ers_force_destroy_all - Force the destruction of all the managers. *
|
|
\*****************************************************************************/
|
|
|
|
/**
|
|
* Get a new instance of the manager that handles the specified entry size.
|
|
* Size has to greater than 0.
|
|
* If the specified size is smaller than a pointer, the size of a pointer is
|
|
* used instead.
|
|
* It's also aligned to ERS_ALIGNED bytes, so the smallest multiple of
|
|
* ERS_ALIGNED that is greater or equal to size is what's actually used.
|
|
* @param The requested size of the entry in bytes
|
|
* @return Interface of the object
|
|
* @public
|
|
* @see #ERSystem
|
|
* @see #ers_root
|
|
* @see #ers_num
|
|
* @see common\ers.h#ERInterface
|
|
* @see common\ers.h\ERInterface#destroy(ERInterface)
|
|
* @see common\ers.h#ers_new_(uint32)
|
|
*/
|
|
ERInterface ers_new(uint32 size)
|
|
{
|
|
ERSystem obj;
|
|
uint32 i;
|
|
|
|
if (size == 0) {
|
|
ShowError("ers_new: invalid size %u, aborting instance creation.\n",
|
|
size);
|
|
return NULL;
|
|
}
|
|
|
|
if (size < sizeof(struct ers_ll)) // Minimum size
|
|
size = sizeof(struct ers_ll);
|
|
if (size%ERS_ALIGNED) // Align size
|
|
size += ERS_ALIGNED -size%ERS_ALIGNED;
|
|
|
|
for (i = 0; i < ers_num; i++) {
|
|
obj = ers_root[i];
|
|
if (obj->size == size) {
|
|
// found a manager that handles the entry size
|
|
obj->destroy++;
|
|
return &obj->eri;
|
|
}
|
|
}
|
|
// create a new manager to handle the entry size
|
|
if (ers_num == ERS_ROOT_SIZE) {
|
|
ShowFatalError("ers_alloc: too many root objects, increase ERS_ROOT_SIZE.\n"
|
|
"exiting the program...\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
obj = (ERSystem)aMalloc(sizeof(struct ers));
|
|
// Public interface
|
|
obj->eri.alloc = ers_obj_alloc_entry;
|
|
obj->eri.free = ers_obj_free_entry;
|
|
obj->eri.entry_size = ers_obj_entry_size;
|
|
obj->eri.destroy = ers_obj_destroy;
|
|
// Block reusage system
|
|
obj->reuse = NULL;
|
|
obj->blocks = NULL;
|
|
obj->free = 0;
|
|
obj->num = 0;
|
|
obj->max = 0;
|
|
obj->destroy = 1;
|
|
// Properties
|
|
obj->size = size;
|
|
ers_root[ers_num++] = obj;
|
|
return &obj->eri;
|
|
}
|
|
|
|
/**
|
|
* Print a report about the current state of the Entry Reusage System.
|
|
* Shows information about the global system and each entry manager.
|
|
* The number of entries are checked and a warning is shown if extra reusable
|
|
* entries are found.
|
|
* The extra entries are included in the count of reusable entries.
|
|
* @public
|
|
* @see #ERLinkedList
|
|
* @see #ERSystem
|
|
* @see #ers_root
|
|
* @see #ers_num
|
|
* @see common\ers.h#ers_report(void)
|
|
*/
|
|
void ers_report(void)
|
|
{
|
|
uint32 i, j, used, reusable, extra;
|
|
ERLinkedList reuse;
|
|
ERSystem obj;
|
|
|
|
// Root system report
|
|
ShowMessage(CL_BOLD"Entry Reusage System report:\n"CL_NORMAL);
|
|
ShowMessage("root array size : %u\n", ERS_ROOT_SIZE);
|
|
ShowMessage("root entry managers : %u\n", ers_num);
|
|
ShowMessage("entries per block : %u\n", ERS_BLOCK_ENTRIES);
|
|
for (i = 0; i < ers_num; i++) {
|
|
obj = ers_root[i];
|
|
reuse = obj->reuse;
|
|
used = 0;
|
|
reusable = 0;
|
|
// Count used and reusable entries
|
|
for (j = 0; j < obj->num; j++) {
|
|
if (j == 0) { // take into acount the free entries
|
|
used = ERS_BLOCK_ENTRIES -obj->free;
|
|
} else if (reuse) { // counting reusable entries
|
|
used = ERS_BLOCK_ENTRIES;
|
|
} else { // no more reusable entries, count remaining used entries
|
|
for (; j < obj->num; j++) {
|
|
if (used > UINT32_MAX -ERS_BLOCK_ENTRIES) { // overflow
|
|
used = UINT32_MAX;
|
|
break;
|
|
}
|
|
used += ERS_BLOCK_ENTRIES;
|
|
}
|
|
break;
|
|
}
|
|
while (used && reuse) { // count reusable entries
|
|
used--;
|
|
if (reusable != UINT32_MAX)
|
|
reusable++;
|
|
reuse = reuse->next;
|
|
}
|
|
}
|
|
// Count extra reusable entries
|
|
extra = 0;
|
|
while (reuse && extra != UINT32_MAX) {
|
|
extra++;
|
|
reuse = reuse->next;
|
|
}
|
|
// Entry manager report
|
|
ShowMessage(CL_BOLD"[Entry manager #%u report]\n"CL_NORMAL, i);
|
|
ShowMessage("\tinstances : %u\n", obj->destroy);
|
|
ShowMessage("\tentry size : %u\n", obj->size);
|
|
ShowMessage("\tblock array size : %u\n", obj->max);
|
|
ShowMessage("\tallocated blocks : %u\n", obj->num);
|
|
ShowMessage("\tentries being used : %u\n", used);
|
|
ShowMessage("\tunused entries : %u\n", obj->free);
|
|
ShowMessage("\treusable entries : %u\n", reusable);
|
|
if (extra)
|
|
ShowMessage("\tWARNING - %u extra reusable entries were found.\n", extra);
|
|
}
|
|
ShowMessage("End of report\n");
|
|
}
|
|
|
|
/**
|
|
* Forcibly destroy all the entry managers, checking for nothing.
|
|
* The system is left as if no instances or entries had ever been allocated.
|
|
* All previous entries and instances of the managers become invalid.
|
|
* The use of this is NOT recommended.
|
|
* It should only be used in extreme situations to make shure all the memory
|
|
* allocated by this system is released.
|
|
* @public
|
|
* @see #ERSystem
|
|
* @see #ers_root
|
|
* @see #ers_num
|
|
* @see common\ers.h#ers_force_destroy_all(void)
|
|
*/
|
|
void ers_force_destroy_all(void)
|
|
{
|
|
uint32 i, j;
|
|
ERSystem obj;
|
|
|
|
for (i = 0; i < ers_num; i++) {
|
|
obj = ers_root[i];
|
|
if (obj->max) {
|
|
for (j = 0; j < obj->num; j++)
|
|
aFree(obj->blocks[j]); // block of entries
|
|
aFree(obj->blocks); // array of blocks
|
|
}
|
|
aFree(obj); // entry manager object
|
|
}
|
|
ers_num = 0;
|
|
}
|
|
#endif /* not DISABLE_ERS */
|
|
|