Skip to content

Instantly share code, notes, and snippets.

@adammyhre
Created June 8, 2025 07:18
Show Gist options
  • Save adammyhre/b94637dc8ee5e272b05375c61da6d161 to your computer and use it in GitHub Desktop.
Save adammyhre/b94637dc8ee5e272b05375c61da6d161 to your computer and use it in GitHub Desktop.
Unity Memory Arena
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
public unsafe class ArenaAllocator : IDisposable {
byte* buffer; // 'buffer' is a pointer to a byte (i.e., byte*)
int offset;
readonly int capacity;
public ArenaAllocator(int sizeInBytes) {
buffer = (byte*)UnsafeUtility.Malloc(sizeInBytes, 16, Allocator.Persistent);
offset = 0;
capacity = sizeInBytes;
}
public T* Alloc<T>(int count = 1) where T : unmanaged {
int size = UnsafeUtility.SizeOf<T>() * count;
if (offset + size > capacity) throw new Exception("Arena overflow");
T* ptr = (T*)(buffer + offset);
offset += size;
return ptr;
}
public void Reset() => offset = 0;
public void Dispose() {
if (buffer != null) {
UnsafeUtility.Free(buffer, Allocator.Persistent);
buffer = null;
}
offset = 0;
}
}
public unsafe struct CraftNode {
public ItemType outputType;
public int amountNeeded;
public int amountAvailable;
public CraftNode** subIngredients;
public int subCount;
}
public unsafe class CraftSimulator {
readonly RecipeBook book;
readonly Inventory inventory;
public CraftSimulator(RecipeBook book, Inventory inventory) {
this.book = book;
this.inventory = inventory;
}
public CraftNode* SimulateCraft(ArenaAllocator arena, ItemType item, int amountNeeded) {
CraftNode* node = arena.Alloc<CraftNode>();
node->outputType = item;
node->amountNeeded = amountNeeded;
if (book.TryGetRecipe(item, out var recipe)) {
node->subCount = recipe.ingredients.Length;
node->subIngredients = (CraftNode**)arena.Alloc<byte>(sizeof(CraftNode*) * node->subCount);
int maxCraftable = int.MaxValue;
for (int i = 0; i < recipe.ingredients.Length; i++) {
var ingredient = recipe.ingredients[i];
int requiredAmount = ingredient.count * amountNeeded;
CraftNode* sub = SimulateCraft(arena, ingredient.type, requiredAmount);
node->subIngredients[i] = sub;
int possible = sub->amountAvailable / ingredient.count;
if (possible < maxCraftable) maxCraftable = possible;
}
node->amountAvailable = maxCraftable;
}
else {
node->subCount = 0;
node->subIngredients = null;
node->amountAvailable = inventory.GetCount(item);
}
return node;
}
}
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
public unsafe class Example : MonoBehaviour {
RecipeBook book;
Inventory inventory;
ArenaAllocator arena;
CraftSimulator simulator;
void Start() {
book = new RecipeBook();
inventory = new Inventory();
arena = new ArenaAllocator(UnsafeUtility.SizeOf<CraftNode>() * 10);
simulator = new CraftSimulator(book, inventory);
book.Add(new Recipe(ItemType.IronIngot, new[] {
new Ingredient { type = ItemType.IronOre, count = 2 }
}));
book.Add(new Recipe(ItemType.IronSword, new[] {
new Ingredient { type = ItemType.IronIngot, count = 3 },
new Ingredient { type = ItemType.Stick, count = 1 }
}));
inventory.Add(ItemType.IronOre, 10);
inventory.Add(ItemType.Stick, 3);
CraftNode* root = simulator.SimulateCraft(arena, ItemType.IronSword, 1);
Debug.Log($"Can craft {root->outputType}: {root->amountAvailable}/{root->amountNeeded}");
for (int i = 0; i < root->subCount; i++) {
CraftNode* sub = root->subIngredients[i];
Debug.Log($" -> {sub->outputType}: {sub->amountAvailable}/{sub->amountNeeded}");
}
arena.Reset();
}
void OnDestroy() => arena.Dispose();
}
using System.Collections.Generic;
public enum ItemType { IronOre, IronIngot, IronSword, Wood, Stick }
public struct Ingredient {
public ItemType type;
public int count;
}
public class RecipeBook {
readonly Dictionary<ItemType, Recipe> recipes = new();
public void Add(Recipe recipe) => recipes[recipe.output] = recipe;
public bool TryGetRecipe(ItemType type, out Recipe recipe) => recipes.TryGetValue(type, out recipe);
}
public class Recipe {
public ItemType output;
public Ingredient[] ingredients;
public Recipe(ItemType output, Ingredient[] ingredients) {
this.output = output;
this.ingredients = ingredients;
}
}
public class Inventory {
readonly Dictionary<ItemType, int> stock = new();
public void Add(ItemType type, int count) {
stock.TryAdd(type, 0);
stock[type] += count;
}
public int GetCount(ItemType type) => stock.GetValueOrDefault(type, 0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment