Install
$ agentstack add skill-bullish0x-gamestudio-memory-management ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Mobile Memory Management
When to Use
Use this skill when:
- Optimizing for mobile memory constraints
- Preventing memory leaks
- Managing texture memory
- Implementing object pooling
- Handling low memory warnings
- Optimizing garbage collection
Core Principles
- Proactive Disposal: Clean up resources explicitly
- Object Pooling: Reuse instead of allocate
- Texture Management: Compress and limit VRAM
- Lazy Loading: Load resources on demand
- Memory Monitoring: Track and respond to pressure
- GC Optimization: Minimize garbage collection pauses
Memory Management Implementation
1. Resource Manager
// memory/ResourceManager.ts
export interface Disposable {
dispose(): void;
}
export class ResourceManager {
private resources = new Map();
private refCounts = new Map();
private memoryUsage = 0;
register(id: string, resource: T): T {
if (this.resources.has(id)) {
// Resource already exists, increment ref count
this.refCounts.set(id, (this.refCounts.get(id) ?? 0) + 1);
return this.resources.get(id) as T;
}
this.resources.set(id, resource);
this.refCounts.set(id, 1);
// Track memory usage
if ('memory' in resource) {
this.memoryUsage += (resource as any).memory;
}
return resource;
}
get(id: string): T | undefined {
return this.resources.get(id) as T | undefined;
}
release(id: string): void {
const refCount = this.refCounts.get(id) ?? 0;
if (refCount {
const resources: Array = [];
for (const [id, resource] of this.resources) {
if ('memory' in resource) {
resources.push({ id, memory: (resource as any).memory });
}
}
return resources.sort((a, b) => b.memory - a.memory);
}
}
2. Texture Memory Manager
// memory/TextureManager.ts
export class TextureManager {
private textures = new Map();
private vramUsage = 0;
private readonly MAX_VRAM_MB = 256; // Mobile limit
constructor(private resourceManager: ResourceManager) {}
async load(url: string, options: { compress?: boolean } = {}): Promise {
// Check if already loaded
if (this.textures.has(url)) {
return this.textures.get(url)!;
}
// Check VRAM limit
if (this.vramUsage >= this.MAX_VRAM_MB * 1024 * 1024) {
this.evictLeastRecentlyUsed();
}
const loader = new THREE.TextureLoader();
const texture = await loader.loadAsync(url);
// Apply mobile-friendly settings
texture.generateMipmaps = true;
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
// Compress if requested
if (options.compress) {
this.applyCompression(texture);
}
// Calculate and track VRAM usage
const memorySize = this.calculateTextureSize(texture);
this.vramUsage += memorySize;
// Register with resource manager
this.resourceManager.register(url, {
dispose: () => {
texture.dispose();
this.vramUsage -= memorySize;
this.textures.delete(url);
},
});
this.textures.set(url, texture);
return texture;
}
private calculateTextureSize(texture: THREE.Texture): number {
const image = texture.image;
if (!image) return 0;
const width = image.width || 512;
const height = image.height || 512;
// RGBA = 4 bytes per pixel, plus mipmaps (~1.33x)
return width * height * 4 * 1.33;
}
private applyCompression(texture: THREE.Texture): void {
// Note: Actual compression requires compressed texture formats
// This is a placeholder for format selection logic
// For mobile, prefer:
// - ASTC on iOS
// - ETC2 on Android
// - Fallback to lower resolution
// Example: Reduce resolution
if (texture.image) {
const maxSize = 1024;
if (texture.image.width > maxSize || texture.image.height > maxSize) {
texture.image.width = Math.min(texture.image.width, maxSize);
texture.image.height = Math.min(texture.image.height, maxSize);
texture.needsUpdate = true;
}
}
}
private evictLeastRecentlyUsed(): void {
// Simple strategy: remove oldest texture
const firstKey = this.textures.keys().next().value;
if (firstKey) {
this.resourceManager.release(firstKey);
}
}
release(url: string): void {
this.resourceManager.release(url);
}
getVRAMUsage(): number {
return this.vramUsage;
}
getVRAMLimit(): number {
return this.MAX_VRAM_MB * 1024 * 1024;
}
getVRAMUsagePercent(): number {
return (this.vramUsage / this.getVRAMLimit()) * 100;
}
}
3. Object Pooling
// memory/ObjectPool.ts
export interface Poolable {
reset(): void;
}
export class ObjectPool {
private pool: T[] = [];
private active = new Set();
private factory: () => T;
private initialSize: number;
private maxSize: number;
constructor(
factory: () => T,
initialSize: number = 10,
maxSize: number = 100
) {
this.factory = factory;
this.initialSize = initialSize;
this.maxSize = maxSize;
// Pre-allocate initial pool
for (let i = 0; i 0) {
obj = this.pool.pop()!;
} else if (this.active.size );
}
this.entity.active = false;
}
}
4. Memory Monitor
// memory/MemoryMonitor.ts
export interface MemoryInfo {
usedJSHeapSize: number; // Bytes
totalJSHeapSize: number;
jsHeapSizeLimit: number;
usagePercent: number;
}
export class MemoryMonitor {
private checkInterval: number;
private intervalId: number | null = null;
private listeners = new Set void>();
private warningThreshold = 0.85; // 85% usage
private criticalThreshold = 0.95; // 95% usage
constructor(checkInterval: number = 5000) {
this.checkInterval = checkInterval;
}
start(): void {
if (this.intervalId !== null) return;
this.intervalId = window.setInterval(() => {
this.checkMemory();
}, this.checkInterval);
// Initial check
this.checkMemory();
}
stop(): void {
if (this.intervalId !== null) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
private checkMemory(): void {
const info = this.getMemoryInfo();
// Notify listeners
for (const listener of this.listeners) {
listener(info);
}
// Check thresholds
if (info.usagePercent >= this.criticalThreshold) {
this.onCriticalMemory();
} else if (info.usagePercent >= this.warningThreshold) {
this.onWarningMemory();
}
}
getMemoryInfo(): MemoryInfo {
if ('memory' in performance) {
const memory = (performance as any).memory;
return {
usedJSHeapSize: memory.usedJSHeapSize,
totalJSHeapSize: memory.totalJSHeapSize,
jsHeapSizeLimit: memory.jsHeapSizeLimit,
usagePercent: (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100,
};
}
// Fallback for browsers without memory API
return {
usedJSHeapSize: 0,
totalJSHeapSize: 0,
jsHeapSizeLimit: 0,
usagePercent: 0,
};
}
private onWarningMemory(): void {
console.warn('Memory usage above warning threshold');
// Trigger gentle cleanup
}
private onCriticalMemory(): void {
console.error('Memory usage critical!');
// Trigger aggressive cleanup
this.forceGarbageCollection();
}
private forceGarbageCollection(): void {
// Trigger GC indirectly by nulling references
// Note: Can't force GC in JavaScript, but can encourage it
// Clear caches, pools, etc.
window.dispatchEvent(new CustomEvent('memory:critical'));
}
onMemoryChange(listener: (info: MemoryInfo) => void): () => void {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
setWarningThreshold(percent: number): void {
this.warningThreshold = Math.max(0, Math.min(1, percent));
}
setCriticalThreshold(percent: number): void {
this.criticalThreshold = Math.max(0, Math.min(1, percent));
}
}
5. Geometry Pool Manager
// memory/GeometryManager.ts
export class GeometryManager {
private geometries = new Map();
private instances = new Map();
constructor(private resourceManager: ResourceManager) {}
createBox(width: number, height: number, depth: number): THREE.BufferGeometry {
const key = `box:${width}:${height}:${depth}`;
return this.getOrCreate(key, () => new THREE.BoxGeometry(width, height, depth));
}
createSphere(radius: number, segments: number = 32): THREE.BufferGeometry {
const key = `sphere:${radius}:${segments}`;
return this.getOrCreate(key, () => new THREE.SphereGeometry(radius, segments, segments));
}
createPlane(width: number, height: number): THREE.BufferGeometry {
const key = `plane:${width}:${height}`;
return this.getOrCreate(key, () => new THREE.PlaneGeometry(width, height));
}
private getOrCreate(key: string, factory: () => THREE.BufferGeometry): THREE.BufferGeometry {
let geometry = this.geometries.get(key);
if (!geometry) {
geometry = factory();
this.geometries.set(key, geometry);
this.instances.set(geometry, 0);
// Register for cleanup
this.resourceManager.register(key, {
dispose: () => {
geometry!.dispose();
this.geometries.delete(key);
this.instances.delete(geometry!);
},
});
}
// Increment instance count
this.instances.set(geometry, (this.instances.get(geometry) ?? 0) + 1);
return geometry;
}
release(geometry: THREE.BufferGeometry): void {
const count = (this.instances.get(geometry) ?? 0) - 1;
if (count ();
private instances = new Map();
constructor(private resourceManager: ResourceManager) {}
createStandard(options: {
color?: THREE.ColorRepresentation;
map?: THREE.Texture;
metalness?: number;
roughness?: number;
}): THREE.MeshStandardMaterial {
const key = this.hashOptions('standard', options);
return this.getOrCreate(key, () => new THREE.MeshStandardMaterial(options));
}
createBasic(options: {
color?: THREE.ColorRepresentation;
map?: THREE.Texture;
}): THREE.MeshBasicMaterial {
const key = this.hashOptions('basic', options);
return this.getOrCreate(key, () => new THREE.MeshBasicMaterial(options));
}
private hashOptions(type: string, options: any): string {
// Simple hash of material options
return `${type}:${JSON.stringify(options)}`;
}
private getOrCreate(key: string, factory: () => T): T {
let material = this.materials.get(key) as T;
if (!material) {
material = factory();
this.materials.set(key, material);
this.instances.set(material, 0);
// Register for cleanup
this.resourceManager.register(key, {
dispose: () => {
material!.dispose();
this.materials.delete(key);
this.instances.delete(material!);
},
});
}
// Increment instance count
this.instances.set(material, (this.instances.get(material) ?? 0) + 1);
return material;
}
release(material: THREE.Material): void {
const count = (this.instances.get(material) ?? 0) - 1;
if (count new PooledEntity(world), 50, 200);
// Spawn entity
const pooled = entityPool.acquire();
const entity = pooled.entity;
entity.addComponent(new Transform(new Vector3(0, 0, 0)));
entity.active = true;
// Return to pool when destroyed
entity.active = false;
entityPool.release(pooled);
// Example 5: Memory monitoring
const memoryMonitor = new MemoryMonitor(5000);
memoryMonitor.onMemoryChange((info) => {
console.log(`Memory usage: ${info.usagePercent.toFixed(1)}%`);
if (info.usagePercent > 90) {
// Aggressive cleanup
entityPool.releaseAll();
textureManager.getResourcesByMemory().slice(0, 10).forEach((res) => {
resourceManager.release(res.id);
});
}
});
memoryMonitor.start();
// Example 6: Respond to low memory
window.addEventListener('memory:critical', () => {
// Clear all pools
entityPool.clear();
// Release unused textures
textureManager.evictLeastRecentlyUsed();
// Clear geometry cache
geometryManager.clear();
// Clear material cache
materialManager.clear();
// Force scene cleanup
scene.traverse((obj) => {
if (obj instanceof THREE.Mesh) {
obj.geometry.dispose();
if (Array.isArray(obj.material)) {
obj.material.forEach((mat) => mat.dispose());
} else {
obj.material.dispose();
}
}
});
});
// Example 7: Disposable scene manager
class DisposableScene {
private scene: THREE.Scene;
private objects = new Set();
constructor() {
this.scene = new THREE.Scene();
}
add(object: THREE.Object3D): void {
this.scene.add(object);
this.objects.add(object);
}
remove(object: THREE.Object3D): void {
this.scene.remove(object);
this.objects.delete(object);
this.disposeObject(object);
}
private disposeObject(object: THREE.Object3D): void {
object.traverse((child) => {
if (child instanceof THREE.Mesh) {
geometryManager.release(child.geometry);
if (Array.isArray(child.material)) {
child.material.forEach((mat) => materialManager.release(mat));
} else {
materialManager.release(child.material);
}
}
});
}
dispose(): void {
for (const object of this.objects) {
this.disposeObject(object);
}
this.objects.clear();
this.scene.clear();
}
}
Checklist
- [ ] Implement ResourceManager
- [ ] Create TextureManager with VRAM tracking
- [ ] Set up object pooling
- [ ] Add memory monitoring
- [ ] Create geometry/material managers
- [ ] Handle low memory events
- [ ] Implement proper disposal
- [ ] Test for memory leaks
- [ ] Profile memory usage
- [ ] Add memory usage UI
Common Pitfalls
- Missing dispose calls: Memory leaks
- No ref counting: Premature disposal
- Unbounded pools: Memory growth
- Texture duplication: Wasted VRAM
- No memory monitoring: Crashes on low memory
- Event listener leaks: Accumulating handlers
- Circular references: GC can't collect
Performance Tips
Memory Efficiency
- Pool frequently created objects
- Share geometries and materials
- Compress textures for mobile
- Monitor VRAM usage
- Dispose resources explicitly
Leak Prevention
- Use ref counting for shared resources
- Remove event listeners
- Clear intervals and timeouts
- Null references when done
- Profile with DevTools
GC Optimization
- Minimize allocations in hot paths
- Reuse objects via pooling
- Avoid creating temporary objects
- Use typed arrays
- Batch operations
Mobile Considerations
- Lower VRAM limits (256MB typical)
- More aggressive texture compression
- Smaller object pools
- More frequent cleanup
- Monitor device memory API
Related Skills
battery-optimization- Power managementmobile-performance- General optimizationthreejs-texture-management- Texture optimizationthreejs-geometry-management- Geometry sharingecs-performance- ECS memory patterns
References
- JavaScript memory management
- WebGL memory limits
- Mobile VRAM constraints
- Object pooling patterns
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: bullish0x
- Source: bullish0x/GameStudio
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.