PageRenderTime 59ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/code/render/resources/resourceallocator.cc

http://nebula3.googlecode.com/
C++ | 380 lines | 239 code | 33 blank | 108 comment | 38 complexity | 849f6229b9d98680e923d3db5ae81798 MD5 | raw file
Possible License(s): 0BSD
  1. //------------------------------------------------------------------------------
  2. // resourceallocator.cc
  3. // (C) 2008 Radon Labs GmbH
  4. //------------------------------------------------------------------------------
  5. #include "stdneb.h"
  6. #include "resources/resourceallocator.h"
  7. #include "resources/resource.h"
  8. namespace Resources
  9. {
  10. __ImplementClass(Resources::ResourceAllocator, 'RSAC', Core::RefCounted);
  11. using namespace Util;
  12. //------------------------------------------------------------------------------
  13. /**
  14. */
  15. ResourceAllocator::ResourceAllocator() :
  16. arenaBlockSize(0),
  17. arenaNumBlocks(0),
  18. arenaStartAddress(0),
  19. isValid(false)
  20. {
  21. this->lumps.Reserve(512);
  22. }
  23. //------------------------------------------------------------------------------
  24. /**
  25. */
  26. ResourceAllocator::~ResourceAllocator()
  27. {
  28. n_assert(!this->IsValid());
  29. }
  30. //------------------------------------------------------------------------------
  31. /**
  32. */
  33. void
  34. ResourceAllocator::Setup(SizeT blockSize, SizeT numBlocks)
  35. {
  36. n_assert(!this->IsValid());
  37. n_assert(blockSize > 0);
  38. n_assert(numBlocks > 0);
  39. this->isValid = true;
  40. this->arenaBlockSize = blockSize;
  41. this->arenaNumBlocks = numBlocks;
  42. this->arenaStartAddress = (uchar*) this->AllocateArena(this->arenaBlockSize * this->arenaNumBlocks);
  43. // setup one free lump which spans the entire memory arena
  44. this->lumps.Append(this->CreateLump(0, this->arenaNumBlocks));
  45. }
  46. //------------------------------------------------------------------------------
  47. /**
  48. */
  49. void
  50. ResourceAllocator::Discard()
  51. {
  52. n_assert(this->IsValid());
  53. // release free lumps
  54. IndexT i;
  55. for (i = 0; i < this->lumps.Size(); i++)
  56. {
  57. n_assert(!this->lumps[i]->IsBound());
  58. this->lumps[i]->Discard();
  59. }
  60. this->lumps.Clear();
  61. this->arenaBlockSize = 0;
  62. this->arenaNumBlocks = 0;
  63. this->FreeArena(this->arenaStartAddress);
  64. this->arenaStartAddress = 0;
  65. this->isValid = false;
  66. }
  67. //------------------------------------------------------------------------------
  68. /**
  69. This methods searches the freeLumps array for the lump which would waste
  70. the least memory for the requested block size. If absolutely no lump
  71. is found, the method returns InvalidIndex.
  72. */
  73. IndexT
  74. ResourceAllocator::FindBestFreeLumpIndex(SizeT requiredNumBlocks) const
  75. {
  76. // first find the first lump which can satisfy the request...
  77. IndexT bestLumpIndex = InvalidIndex;
  78. IndexT lumpIndex;
  79. SizeT numLumps = this->lumps.Size();
  80. for (lumpIndex = 0; lumpIndex < numLumps; lumpIndex++)
  81. {
  82. const Ptr<ResourceLump>& curLump = this->lumps[lumpIndex];
  83. if (!curLump->IsBound())
  84. {
  85. if (curLump->GetNumBlocks() >= requiredNumBlocks)
  86. {
  87. bestLumpIndex = lumpIndex;
  88. break;
  89. }
  90. }
  91. }
  92. if (bestLumpIndex != InvalidIndex)
  93. {
  94. // see if we can find a better lump
  95. for (; lumpIndex < numLumps; lumpIndex++)
  96. {
  97. if (!this->lumps[lumpIndex]->IsBound())
  98. {
  99. SizeT curNumBlocks = this->lumps[lumpIndex]->GetNumBlocks();
  100. SizeT bestNumBlocks = this->lumps[bestLumpIndex]->GetNumBlocks();
  101. if ((curNumBlocks >= requiredNumBlocks) && (curNumBlocks < bestNumBlocks))
  102. {
  103. bestLumpIndex = lumpIndex;
  104. }
  105. }
  106. }
  107. }
  108. return bestLumpIndex;
  109. }
  110. //------------------------------------------------------------------------------
  111. /**
  112. This method tries to allocate a lump from the resource arena and
  113. bind it to a resource. If the resource arena is too fragmented to
  114. satisfy the request it will be compacted and the method will retry. If
  115. this fails again, the method will return an invalid ptr.
  116. */
  117. Ptr<ResourceLump>
  118. ResourceAllocator::AllocateLump(const Ptr<Resource>& res, SizeT numBlocks)
  119. {
  120. n_assert(this->IsValid());
  121. // find the best free lump which is big enough
  122. IndexT bestFreeLump = this->FindBestFreeLumpIndex(numBlocks);
  123. if (InvalidIndex == bestFreeLump)
  124. {
  125. // hmm, the arena may be full, or fragmented, so let's defragment
  126. // the arena and try again...
  127. this->Compact();
  128. bestFreeLump = this->FindBestFreeLumpIndex(numBlocks);
  129. if (InvalidIndex == bestFreeLump)
  130. {
  131. // nope, we definitely can't satisfy the request
  132. return Ptr<ResourceLump>();
  133. }
  134. }
  135. // this is definitely the lump we will settle on, but we will very likely
  136. // have to split the lump since it is to big
  137. Ptr<ResourceLump> lump = this->lumps[bestFreeLump];
  138. n_assert(!lump->IsBound());
  139. // if the best lump doesn't exactly satisfy the request, split it into two lumps
  140. IndexT lumpFirstBlockIndex = lump->GetFirstBlockIndex();
  141. SizeT lumpNumBlocks = lump->GetNumBlocks();
  142. n_assert(lumpNumBlocks >= numBlocks);
  143. if (lumpNumBlocks > numBlocks)
  144. {
  145. // resize the existing lump to exactly the right size,
  146. // and create a new free lump from the remaining blocks
  147. lump->Resize(numBlocks);
  148. SizeT diffNumBlocks = lumpNumBlocks - numBlocks;
  149. Ptr<ResourceLump> newLump = this->CreateLump(lumpFirstBlockIndex + numBlocks, diffNumBlocks);
  150. this->lumps.Append(newLump);
  151. }
  152. // lump points to the right blocks and has exactly the right size
  153. lump->Bind(res);
  154. return lump;
  155. }
  156. //------------------------------------------------------------------------------
  157. /**
  158. Compact the resource arena to eliminate defragmentation. This will
  159. remove any existing free lumps and move the bound lumps around to
  160. fill the gaps. At the end of the arena, a single free lump will be
  161. created which spans all the available space in the resource arena.
  162. The method will return the number of continuous free blocks after
  163. the compact operation.
  164. NOTE: the method will not move locked lumps around, this may
  165. result in non-optimal compacting.
  166. */
  167. SizeT
  168. ResourceAllocator::Compact()
  169. {
  170. n_assert(this->IsValid());
  171. // create a sorted-by-first-block array of all free lumps
  172. SizeT numLumps = this->lumps.Size();
  173. Array<KeyValuePair<IndexT,IndexT> > sortedLumpIndices;
  174. sortedLumpIndices.Reserve(numLumps);
  175. IndexT i;
  176. for (i = 0; i < numLumps; i++)
  177. {
  178. sortedLumpIndices.Append(KeyValuePair<IndexT,IndexT>(this->lumps[i]->GetFirstBlockIndex(), i));
  179. }
  180. sortedLumpIndices.Sort();
  181. // we need to keep an array of the free areas that should be created
  182. // after compacting
  183. Array<KeyValuePair<IndexT,SizeT> > createFreeLumps;
  184. // now go through the lumps in sorted order and remove the gaps
  185. IndexT firstFreeBlockIndex = InvalidIndex;
  186. SizeT numFreeBlocks = 0;
  187. for (i = 0; i < sortedLumpIndices.Size(); i++)
  188. {
  189. IndexT lumpIndex = sortedLumpIndices[i].Value();
  190. if (!this->lumps[lumpIndex]->IsBound())
  191. {
  192. // we found a free lump, simply increment the number of blocks
  193. // to move and leave the lump alone for now (free lumps will
  194. // be removed in a second pass after compaction)
  195. numFreeBlocks += this->lumps[lumpIndex]->GetNumBlocks();
  196. }
  197. else
  198. {
  199. // a bound lump will be moved backward to fill the free space,
  200. // but only if it isn't locked, if we encounter a locked
  201. // lump, the move block counter must be reset to zero
  202. if (this->lumps[lumpIndex]->IsLocked())
  203. {
  204. // record the current progress in free lumps array
  205. if ((InvalidIndex != firstFreeBlockIndex) && (numFreeBlocks > 0))
  206. {
  207. createFreeLumps.Append(KeyValuePair<IndexT,SizeT>(firstFreeBlockIndex, numFreeBlocks));
  208. }
  209. numFreeBlocks = 0;
  210. }
  211. else if (numFreeBlocks > 0)
  212. {
  213. IndexT fromBlockIndex = this->lumps[lumpIndex]->GetFirstBlockIndex();
  214. n_assert(fromBlockIndex >= numFreeBlocks);
  215. IndexT toBlockIndex = fromBlockIndex - numFreeBlocks;
  216. SizeT numBlocks = this->lumps[lumpIndex]->GetNumBlocks();
  217. this->MoveArenaBlocks(fromBlockIndex, toBlockIndex, numBlocks);
  218. this->lumps[lumpIndex]->OnMoved(toBlockIndex);
  219. firstFreeBlockIndex = toBlockIndex + numBlocks;
  220. }
  221. }
  222. }
  223. // add the final new free lump at the end of the create-array
  224. if ((InvalidIndex != firstFreeBlockIndex) && (numFreeBlocks > 0))
  225. {
  226. createFreeLumps.Append(KeyValuePair<IndexT,SizeT>(firstFreeBlockIndex, numFreeBlocks));
  227. }
  228. // now delete all previous free lump objects (they no longer contain valid values)
  229. for (i = this->lumps.Size() - 1; i != InvalidIndex; i--)
  230. {
  231. if (!this->lumps[i]->IsBound())
  232. {
  233. this->lumps[i]->Discard();
  234. this->lumps.EraseIndexSwap(i);
  235. }
  236. }
  237. // finally create a new set of free lumps, which point to the new free
  238. // areas, if there were no locked lumps, then this should be a single
  239. // lump at the end of the arena, otherwise there may be a free lump
  240. // right in front of each locked lump
  241. // while we're at it, find the biggest free lump
  242. SizeT biggestFreeAreaBlocks = 0;
  243. for (i = 0; i < createFreeLumps.Size(); i++)
  244. {
  245. IndexT firstBlockIndex = createFreeLumps[i].Key();
  246. SizeT numBlocks = createFreeLumps[i].Value();
  247. Ptr<ResourceLump> newFreeLump = this->CreateLump(firstBlockIndex, numBlocks);
  248. this->lumps.Append(newFreeLump);
  249. // keep track of biggest free area
  250. if (numBlocks > biggestFreeAreaBlocks)
  251. {
  252. biggestFreeAreaBlocks = numBlocks;
  253. }
  254. }
  255. return biggestFreeAreaBlocks;
  256. }
  257. //------------------------------------------------------------------------------
  258. /**
  259. Free a resource lump, this will just unbind the lump from its resource
  260. which marks it as "free".
  261. */
  262. void
  263. ResourceAllocator::FreeLump(const Ptr<ResourceLump>& lump)
  264. {
  265. n_assert(lump.isvalid() && lump->IsBound());
  266. lump->Unbind();
  267. }
  268. //------------------------------------------------------------------------------
  269. /**
  270. Create and setup a new resource lump object. Override this method
  271. in a subclass to create a specific subclass of ResourceLump.
  272. */
  273. Ptr<ResourceLump>
  274. ResourceAllocator::CreateLump(IndexT firstBlock, SizeT numBlocks) const
  275. {
  276. n_assert((firstBlock + numBlocks) <= this->arenaNumBlocks);
  277. Ptr<ResourceLump> lump = ResourceLump::Create();
  278. lump->Setup(this->arenaStartAddress, firstBlock, numBlocks, this->arenaBlockSize);
  279. return lump;
  280. }
  281. //------------------------------------------------------------------------------
  282. /**
  283. Allocate the resource arena. Subclasses may have to override this
  284. method to perform allocation from special resource memory.
  285. */
  286. void*
  287. ResourceAllocator::AllocateArena(SizeT numBytes)
  288. {
  289. return Memory::Alloc(Memory::ResourceHeap, numBytes);
  290. }
  291. //------------------------------------------------------------------------------
  292. /**
  293. Free the resource arena, subclasses may have to override this
  294. method.
  295. */
  296. void
  297. ResourceAllocator::FreeArena(void* ptr)
  298. {
  299. Memory::Free(Memory::ResourceHeap, ptr);
  300. }
  301. //------------------------------------------------------------------------------
  302. /**
  303. Memory-copy wrapper for resource arena memory. Overwrite in subclass
  304. if needed. The pointers are guaranteed to be block-aligned, and the
  305. size is guaranteed to be a multiple of the block size. The 2 memory
  306. regions are guaranteed to not overlap.
  307. */
  308. void
  309. ResourceAllocator::CopyArenaMemory(const void* from, void* to, SizeT numBytes)
  310. {
  311. Memory::Copy(from, to, numBytes);
  312. }
  313. //------------------------------------------------------------------------------
  314. /**
  315. Move a region of memory within the resource arena. Note that the memory
  316. may overlap! Subclasses must override this method if they need a
  317. special memory copy function.
  318. */
  319. void
  320. ResourceAllocator::MoveArenaBlocks(IndexT fromBlockIndex, IndexT toBlockIndex, SizeT numBlocksToMove)
  321. {
  322. n_assert(this->IsValid());
  323. n_assert(fromBlockIndex != toBlockIndex);
  324. n_assert((fromBlockIndex + numBlocksToMove) < this->arenaNumBlocks);
  325. n_assert((toBlockIndex + numBlocksToMove) < this->arenaNumBlocks);
  326. const uchar* src = this->arenaStartAddress + fromBlockIndex * this->arenaBlockSize;
  327. uchar* dst = this->arenaStartAddress + toBlockIndex * this->arenaBlockSize;
  328. SizeT size = this->arenaBlockSize * numBlocksToMove;
  329. if (fromBlockIndex != toBlockIndex)
  330. {
  331. if (Memory::IsOverlapping(src, size, dst, size))
  332. {
  333. // memory is overlapping, perform a block-wise copy
  334. IndexT i;
  335. for (i = 0; i < numBlocksToMove; i++)
  336. {
  337. this->CopyArenaMemory(src + i * this->arenaBlockSize, dst + i * this->arenaBlockSize, this->arenaBlockSize);
  338. }
  339. }
  340. else
  341. {
  342. // memory not overlapping, copy in one go
  343. this->CopyArenaMemory(src, dst, size);
  344. }
  345. }
  346. }
  347. } // namespace Resources