/*	
 *	clipmemp.c
 *
 *	可変長割り当て・一括開放式のメモリプール
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2016 Naoyuki Sawa
 *
 *	* Sun Oct 16 21:10:38 JST 2016 Naoyuki Sawa
 *	- 1st リリース。
 *	- 既存のclipcoll.cのpool_alloc()で作成するメモリプールも有りますが、異なる特性のメモリプールが必要になったので当モジュールを作成しました。
 *	  clipcoll.cのメモリプールと、当モジュール(clipmemc.c)のメモリプールとの、主な違いは以下の通りです。
 *	  ・clipcoll.cのメモリプールは固定サイズのメモリブロックを割り当てるのに対し、当モジュールのメモリプールは任意のサイズのメモリブロックを割り当てます。
 *	  ・clipcoll.cのメモリプールはメモリブロックを個別に割り当て・解放可能ですが、当モジュールのメモリプールは一括開放だけが可能です。
 *	  ・clipcoll.cのメモリプールは全てのメモリブロックが解放された時に実際にメモリをヒープに返しますが、当モジュールのメモリプールはメモリプールが削除されるまでメモリをヒープに返却しません(一括開放型なので当然です。そうでないとメモリプールの意味が有りません)。
 *	  ・clipcoll.cのメモリプールは参照カウント,確保時コールバック,解放時コールバックの追加機能が有りますが、当モジュールにはそれらの追加機能は有りません。
 */
#include "clip.h"
/****************************************************************************
 *	構造体
 ****************************************************************************/
//チャンク構造体
typedef struct _ST_MemPoolChunk {
	int		chunkWords;		//このチャンクのワード数
	int		allocedWords;		//割り当て済みワード数
//	int		buffer[chunkWords];
} ST_MemPoolChunk;
/****************************************************************************
 *	
 ****************************************************************************/
//メモリプールを作成する。
//[in]
//	chunkBytes		チャンクの基本バイト数
//[out]
//	戻り値			メモリプール
//[note]
//	- MemPool_New()の引数allocBytesは、効率以外には影響しません。
//	  0でも可です。
//	- MemPool_Alloc()の引数allocBytesには、MemPool_New()の引数chunkBytesに指定したサイズよりも、大きなサイズを指定する事も出来ます。
//	  MemPool_New()の引数chunkBytesは、新しいチャンクを作成する時の最小サイズを決定するためのヒントであり、効率以外には影響しません。
//	- MemPool_New()の引数chunkBytesに大きな値を指定するほど、一度に大きなチャンクが作成され速度効率は向上しますが、メモリ効率は低下します(無駄なメモリが確保される可能性が増えるため)。
//	  MemPool_New()の引数chunkBytesに小さな値を指定するほど、calloc()呼び出しの頻度が増えて速度効率が低下しますが、メモリ効率は向上します(無駄なメモリが確保される可能性が減るため)。
//	  ただし、極端に小さな値を指定すると、メモリブロックを確保する度にチャンクを作成する事になり、チャンクの管理領域の分、却ってメモリ効率が低下する事に注意して下さい。
//	  どの位が適切かは、アプリケーションがどの位のメモリブロックを何個確保する予定であるか次第ですが、一般的には、PC環境ならば数十キロバイト、P/ECE環境ならば数キロバイトだと思います。
ST_MemPool* MemPool_New(int chunkBytes) {
	//管理構造体のメモリを確保する。
	ST_MemPool* pMemPool = calloc(1, sizeof(ST_MemPool));
	if(!pMemPool) { DIE(); }	//メモリ不足
	//チャンクの基本ワード数を格納する。
	pMemPool->chunkWords = (chunkBytes + (sizeof(int) - 1)) / sizeof(int);	//バイト単位⇒ワード単位に変換する。
	return pMemPool;
}
/*--------------------------------------------------------------------------*/
static void MemPool_FreeFunc(void* data) {
	ST_MemPoolChunk* pChunk = data;
	free(pChunk);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//メモリプールを削除する。
//[in]
//	pMemPool		メモリプール
void MemPool_Free(ST_MemPool* pMemPool) {
	//チャンクを全て解放する。
	g_slist_free_full(pMemPool->chunkList/*NULL可*/, MemPool_FreeFunc);	//削除関数としてfreeを直接指定してはいけない。詳細は、garray.cの'Wed Apr 20 10:51:26 JST 2016'のコメントを参照して下さい。
	//管理構造体のメモリを解放する。
	free(pMemPool);
}
/*--------------------------------------------------------------------------*/
//メモリプールからメモリブロックを確保する。
//[in]
//	pMemPool		メモリプール
//	allocBytes		メモリブロックのバイト数
//[out]
//	戻り値			確保したメモリブロック
//[note]
//	- MemPool_Alloc()の引数allocBytesには、MemPool_New()の引数chunkBytesに指定したサイズよりも、大きなサイズを指定する事も出来ます。
//	  詳細は、MemPool_New()のコメントを参照して下さい。
void* MemPool_Alloc(ST_MemPool* pMemPool, int allocBytes) {
	int i, allocWords = (allocBytes + (sizeof(int) - 1)) / sizeof(int);	//バイト単位⇒ワード単位に変換する。
	//以下のループを一回,又は,二回実行する。
	for(i = 0; i < 2; i++) {
		{
			GSList* pList;
			//既存のチャンクを走査して…
			for(pList = pMemPool->chunkList; pList; pList = pList->next) {
				ST_MemPoolChunk* pChunk = pList->data;
				//このチャンクから、メモリブロックを割り当て可能ならば…
				if((pChunk->allocedWords + allocWords) <= pChunk->chunkWords) {
					//割り当てたメモリブロックのアドレスを求める。
					int* buffer = (int*)(pChunk + 1);
					void* pMem = &buffer[pChunk->allocedWords];
					//このチャンクの、割り当て済みワード数を増やす。
					pChunk->allocedWords += allocWords;
					//割り当てたメモリブロックのアドレスを返す。
					return pMem;	//ここまで
				}
			}
			//既存のチャンクから、メモリブロックを割り当てられなかったら、以下の処理へ進む。
		}
		{
			//新しいチャンクを作成する。
			// - 新しいチャンクのサイズは、チャンクの基本ワード数,又は,割り当てるメモリブロックのワード数の、大きい方とする。
			int chunkWords = max(pMemPool->chunkWords, allocWords);
			//新しいチャンクのメモリを確保する。
			ST_MemPoolChunk* pChunk = calloc(1, sizeof(ST_MemPoolChunk) + (sizeof(int) * chunkWords));
			if(!pChunk) { DIE(); }	//メモリ不足
			//新しいチャンクの、ワード数を格納する。
			pChunk->chunkWords = chunkWords;
			//新しいチャンクを、チャンクのリストの先頭に追加する。
			// - 必ずしも先頭に追加する必要は無く、末尾に追加しても動作上は問題無いが、先頭に追加する方が効率が良い。
			//   二回目のループで、この新しいチャンクからメモリブロックを割り当てる事が自明であり、先頭に追加しておく方が早く走査されるからである。
			//   また、GSListの特性上、末尾に追加するよりも、先頭に追加する法が高速である。
			//   上記二つの理由により、敢えて末尾に追加するメリットは何も無く、先頭に追加するのが適切である。
			pMemPool->chunkList = g_slist_prepend(pMemPool->chunkList/*NULL可*/, pChunk);
		}
	}
	DIE();	//少なくとも二周目でメモリ確保出来るはず。もしここでエラー停止したら当モジュールのバグです。
}
/*--------------------------------------------------------------------------*/
static void MemPool_ClearFunc(void* data, void* user_data) {
	ST_MemPoolChunk* pChunk = data;
	pChunk->allocedWords = 0;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//このメモリプールから割り当てたメモリブロックを全て、メモリプールへ返却する。
void MemPool_Clear(ST_MemPool* pMemPool) {
	//全てのチャンクの、割り当て済みワード数をリセットする。
	g_slist_foreach(pMemPool->chunkList/*NULL可*/, MemPool_ClearFunc, NULL);
}
