object_pool 1.0.0 copy "object_pool: ^1.0.0" to clipboard
object_pool: ^1.0.0 copied to clipboard

A high-performance generic object pool for Dart and Flutter that reduces garbage collection pressure by reusing pre-allocated objects, ideal for games, animations, particle systems, and performance-cr [...]

example/main.dart

// ignore_for_file: avoid_print

/// Comprehensive usage examples for the `object_pool` package.
///
/// Run with: `dart run example/main.dart`
library;

import 'package:object_pool/object_pool.dart';

// ─── Example 1: Basic Usage ───────────────────────────────────────────────────

class Bullet implements Poolable {
  double x = 0;
  double y = 0;
  double speed = 0;
  int damage = 0;
  bool active = false;

  void fire({
    required double x,
    required double y,
    required double speed,
    required int damage,
  }) {
    this.x = x;
    this.y = y;
    this.speed = speed;
    this.damage = damage;
  }

  @override
  void onAcquire() => active = true;

  @override
  void onRelease() {
    x = 0;
    y = 0;
    speed = 0;
    damage = 0;
    active = false;
  }

  @override
  bool get isValid => true;

  @override
  String toString() =>
      'Bullet(x: ${x.toStringAsFixed(1)}, y: ${y.toStringAsFixed(1)}, '
      'speed: $speed, damage: $damage, active: $active)';
}

void example1BasicUsage() {
  print('\n════════════════════════════════════════');
  print('  Example 1 — Basic Acquire / Release');
  print('════════════════════════════════════════');

  // Create a pool of 10 Bullets, max 50
  final bulletPool = ObjectPool<Bullet>(
    factory: Bullet.new,
    initialSize: 10,
    maxSize: 50,
  );

  print('Pool created: $bulletPool');

  // Acquire a bullet
  final b1 = bulletPool.acquire()!;
  b1.fire(x: 100, y: 200, speed: 15.0, damage: 25);
  print('Acquired  → $b1');
  print('Pool state: ${bulletPool.activeCount} active, '
      '${bulletPool.availableCount} available');

  // Acquire a second
  final b2 = bulletPool.acquire()!;
  b2.fire(x: 300, y: 150, speed: 20.0, damage: 50);
  print('Acquired  → $b2');

  // Release the first — it returns to pool and gets cleaned up
  bulletPool.release(b1);
  print('Released  → b1 is now: $b1');
  print('Pool state: ${bulletPool.activeCount} active, '
      '${bulletPool.availableCount} available');

  // Reuse the same object
  final b3 = bulletPool.acquire()!;
  b3.fire(x: 50, y: 50, speed: 10.0, damage: 5);
  print('Reacquired → same object as b1? ${identical(b1, b3)}');
  print('b3 = $b3');

  bulletPool.release(b2);
  bulletPool.release(b3);
  bulletPool.dispose();
  print('Pool disposed.');
}

// ─── Example 2: Scoped use() — RAII Pattern ───────────────────────────────────

class Matrix4x4 {
  final List<double> data = List.filled(16, 0.0);

  void setIdentity() {
    for (var i = 0; i < 16; i++) {
      data[i] = (i % 5 == 0) ? 1.0 : 0.0;
    }
  }

  double get determinant {
    // Simplified — just for demo
    return data[0] * data[5] * data[10] * data[15];
  }
}

void example2ScopedUse() {
  print('\n════════════════════════════════════════');
  print('  Example 2 — Scoped use() / useAsync()');
  print('════════════════════════════════════════');

  final matrixPool = ObjectPool<Matrix4x4>(
    factory: Matrix4x4.new,
    initialSize: 20,
  );

  // Synchronous scoped use — guaranteed release
  final det = matrixPool.use((m) {
    m.setIdentity();
    return m.determinant;
  });
  print('Matrix determinant: $det');
  print('Active after use(): ${matrixPool.activeCount}');

  // use() when pool is full with DiscardStrategy
  final discardPool = ObjectPool<Matrix4x4>(
    factory: Matrix4x4.new,
    initialSize: 1,
    strategy: const DiscardStrategy(),
  );
  discardPool.acquire(); // exhaust the pool

  final result = discardPool.use((m) => m.determinant);
  print('use() on exhausted pool returned: $result'); // null

  matrixPool.dispose();
  discardPool.dispose();
}

// ─── Example 3: Overflow Strategies ──────────────────────────────────────────

class AudioChannel {
  int id;
  String? currentTrack;
  bool playing = false;
  DateTime? startedAt;

  AudioChannel(this.id);

  void play(String trackName) {
    currentTrack = trackName;
    playing = true;
    startedAt = DateTime.now();
    print('  Channel $id → playing "$trackName"');
  }

  void stop() {
    currentTrack = null;
    playing = false;
    startedAt = null;
  }
}

void example3OverflowStrategies() {
  print('\n════════════════════════════════════════');
  print('  Example 3 — Overflow Strategies');
  print('════════════════════════════════════════');

  var channelId = 0;

  // ── DiscardStrategy
  print('\n[DiscardStrategy] — 4 channels max:');
  final discardPool = ObjectPool<AudioChannel>(
    factory: () => AudioChannel(++channelId),
    initialSize: 4,
    maxSize: 4,
    strategy: const DiscardStrategy(),
  );

  final tracks = ['epic_theme', 'battle_music', 'victory', 'ambient'];
  final channels = tracks.map((t) {
    final ch = discardPool.acquire();
    if (ch != null) {
      ch.play(t);
    } else {
      print('  ✗ Dropped: "$t" (all channels busy)');
    }
    return ch;
  }).toList();

  final overflow = discardPool.acquire();
  print('  5th request: ${overflow == null ? "null (dropped)" : "got channel"}');
  print('  Exhaustions: ${discardPool.metrics.exhaustionCount}');

  for (final ch in channels) {
    if (ch != null) discardPool.release(ch);
  }
  discardPool.dispose();

  // ── ExpandStrategy
  channelId = 0;
  print('\n[ExpandStrategy] — starts with 2, grows to 10:');
  final expandPool = ObjectPool<AudioChannel>(
    factory: () => AudioChannel(++channelId),
    initialSize: 2,
    strategy: ExpandStrategy(maxSize: 10, growthIncrement: 2),
  );

  final expandedChannels = <AudioChannel>[];
  for (var i = 0; i < 6; i++) {
    final ch = expandPool.acquire();
    if (ch != null) {
      ch.play('track_$i');
      expandedChannels.add(ch);
    }
  }
  print('  Pool size after 6 acquires: ${expandPool.size}');
  print('  Expansions: ${expandPool.metrics.expansionCount}');

  for (final ch in expandedChannels) expandPool.release(ch);
  expandPool.dispose();
}

// ─── Example 4: prewarm() for Performance-Critical Moments ───────────────────

class GameEntity implements Poolable {
  int id = 0;
  double x = 0, y = 0;
  String type = '';
  bool alive = false;

  @override
  void onAcquire() => alive = true;

  @override
  void onRelease() {
    id = 0;
    x = 0;
    y = 0;
    type = '';
    alive = false;
  }

  @override
  bool get isValid => true;
}

void example4Prewarm() {
  print('\n════════════════════════════════════════');
  print('  Example 4 — prewarm() / drain()');
  print('════════════════════════════════════════');

  final entityPool = ObjectPool<GameEntity>(
    factory: GameEntity.new,
    initialSize: 10,
    maxSize: 1000,
  );

  print('Initial size: ${entityPool.size}');

  // Simulate "level loading" — prewarm to avoid first-frame allocations
  print('Loading level... prewarm(500)...');
  entityPool.prewarm(500);
  print('Pool ready: ${entityPool.size} entities pre-allocated');

  // Simulate level play — spawn many entities
  final activeEntities = <GameEntity>[];
  for (var i = 0; i < 200; i++) {
    final e = entityPool.acquire()!;
    e.id = i;
    e.x = (i * 7.3) % 1920;
    e.y = (i * 4.1) % 1080;
    e.type = i % 3 == 0 ? 'enemy' : (i % 3 == 1 ? 'projectile' : 'pickup');
    activeEntities.add(e);
  }
  print('Level playing: ${entityPool.activeCount} entities active');
  print('Pool metrics: ${entityPool.metrics.utilizationRate.toStringAsFixed(2)} utilization');

  // Level ends — release all entities
  for (final e in activeEntities) entityPool.release(e);
  print('Level ended: ${entityPool.activeCount} active, ${entityPool.availableCount} available');

  // Drain idle entities to free memory between levels
  entityPool.drain();
  print('After drain: ${entityPool.size} total, ${entityPool.availableCount} available');

  entityPool.dispose();
}

// ─── Example 5: Performance Metrics Dashboard ────────────────────────────────

void example5MetricsDashboard() {
  print('\n════════════════════════════════════════');
  print('  Example 5 — Metrics Dashboard');
  print('════════════════════════════════════════');

  final pool = ObjectPool<Bullet>(
    factory: Bullet.new,
    initialSize: 50,
    maxSize: 200,
  );

  // Simulate 1000 acquire/release cycles
  for (var i = 0; i < 1000; i++) {
    pool.use((b) {
      b.fire(
        x: (i * 3.7) % 1920,
        y: (i * 2.1) % 1080,
        speed: 15.0,
        damage: 10,
      );
    });
  }

  // Simulate some exhaustion
  final held = <Bullet>[];
  for (var i = 0; i < 50; i++) {
    held.add(pool.acquire()!);
  }
  for (var i = 0; i < 5; i++) {
    pool.acquire(); // hits expand strategy
  }
  for (final b in held) pool.release(b);

  print(pool.metrics);

  pool.dispose();
}

// ─── Main ─────────────────────────────────────────────────────────────────────

void main() {
  print('╔══════════════════════════════════════╗');
  print('║  object_pool — Usage Examples        ║');
  print('╚══════════════════════════════════════╝');

  example1BasicUsage();
  example2ScopedUse();
  example3OverflowStrategies();
  example4Prewarm();
  example5MetricsDashboard();

  print('\n✅ All examples completed successfully.\n');
}
0
likes
150
points
5
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A high-performance generic object pool for Dart and Flutter that reduces garbage collection pressure by reusing pre-allocated objects, ideal for games, animations, particle systems, and performance-critical applications.

Repository (GitHub)
View/report issues

Topics

#performance #games #memory #optimization #pooling

License

MIT (license)

Dependencies

meta

More

Packages that depend on object_pool