Comprehensive guide to optimizing the Argonath Systems framework for production deployments.
Performance optimization is critical for production quest systems that handle hundreds of concurrent players, thousands of quests, and millions of events. This guide covers proven optimization techniques for the Argonath framework.
package com.argonath.framework.performance;
import java.lang.management.*;
/**
* JVM profiling utilities for quest systems.
*/
public class QuestProfiler {
private static final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
private static final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
/**
* Profiles quest evaluation performance.
*/
public static ProfileResult profileQuestEvaluation(Quest quest, UUID playerId) {
// Enable CPU time tracking
threadBean.setThreadCpuTimeEnabled(true);
long startCpu = threadBean.getCurrentThreadCpuTime();
long startMem = getCurrentMemoryUsage();
long startTime = System.nanoTime();
try {
// Execute quest evaluation
quest.evaluate(playerId);
} finally {
long endTime = System.nanoTime();
long endCpu = threadBean.getCurrentThreadCpuTime();
long endMem = getCurrentMemoryUsage();
return new ProfileResult(
endTime - startTime, // Wall time
endCpu - startCpu, // CPU time
endMem - startMem, // Memory delta
Thread.currentThread().getId()
);
}
}
private static long getCurrentMemoryUsage() {
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
return heapUsage.getUsed();
}
/**
* Profile result container.
*/
public static class ProfileResult {
public final long wallTimeNanos;
public final long cpuTimeNanos;
public final long memoryBytes;
public final long threadId;
ProfileResult(long wallTime, long cpuTime, long memory, long thread) {
this.wallTimeNanos = wallTime;
this.cpuTimeNanos = cpuTime;
this.memoryBytes = memory;
this.threadId = thread;
}
public double getWallTimeMs() {
return wallTimeNanos / 1_000_000.0;
}
public double getCpuTimeMs() {
return cpuTimeNanos / 1_000_000.0;
}
public double getMemoryKB() {
return memoryBytes / 1024.0;
}
@Override
public String toString() {
return String.format(
"Wall: %.2fms, CPU: %.2fms, Memory: %.2fKB, Thread: %d",
getWallTimeMs(), getCpuTimeMs(), getMemoryKB(), threadId
);
}
}
}
package com.argonath.framework.condition.profiling;
import com.argonath.framework.condition.api.*;
/**
* Wrapper that profiles condition evaluation.
*/
public class ProfiledCondition implements Condition {
private final Condition delegate;
private final String conditionId;
private final MetricsCollector metrics;
// Statistics
private final AtomicLong evaluationCount = new AtomicLong();
private final AtomicLong totalTimeNanos = new AtomicLong();
private final AtomicLong trueCount = new AtomicLong();
public ProfiledCondition(Condition delegate, String id, MetricsCollector metrics) {
this.delegate = delegate;
this.conditionId = id;
this.metrics = metrics;
}
@Override
public boolean evaluate(ConditionContext context) {
long startTime = System.nanoTime();
try {
boolean result = delegate.evaluate(context);
if (result) {
trueCount.incrementAndGet();
}
return result;
} finally {
long duration = System.nanoTime() - startTime;
evaluationCount.incrementAndGet();
totalTimeNanos.addAndGet(duration);
// Record metrics
metrics.recordConditionEvaluation(
conditionId,
delegate.getType(),
duration
);
}
}
@Override
public String getType() {
return delegate.getType();
}
/**
* Returns average evaluation time in nanoseconds.
*/
public double getAverageTimeNanos() {
long count = evaluationCount.get();
return count > 0 ? (double) totalTimeNanos.get() / count : 0;
}
/**
* Returns success rate (0.0 to 1.0).
*/
public double getSuccessRate() {
long count = evaluationCount.get();
return count > 0 ? (double) trueCount.get() / count : 0;
}
/**
* Returns profiling statistics.
*/
public ConditionStats getStats() {
return new ConditionStats(
conditionId,
delegate.getType(),
evaluationCount.get(),
totalTimeNanos.get(),
trueCount.get()
);
}
}
package com.argonath.framework.objective.profiling;
/**
* Profiled objective wrapper for performance analysis.
*/
public class ProfiledObjective implements Objective {
private final Objective delegate;
private final PerformanceMonitor monitor;
private final AtomicLong updateCount = new AtomicLong();
private final AtomicLong successfulUpdates = new AtomicLong();
private final LongAdder totalUpdateTimeNanos = new LongAdder();
@Override
public boolean updateProgress(UUID playerId, ObjectiveEvent event) {
long startTime = System.nanoTime();
try {
boolean updated = delegate.updateProgress(playerId, event);
if (updated) {
successfulUpdates.incrementAndGet();
}
return updated;
} finally {
long duration = System.nanoTime() - startTime;
updateCount.incrementAndGet();
totalUpdateTimeNanos.add(duration);
monitor.recordObjectiveUpdate(
delegate.getId(),
delegate.getType(),
duration
);
}
}
/**
* Returns update throughput (updates per second).
*/
public double getUpdateThroughput(long windowMs) {
// Implementation would track updates over time window
return updateCount.get() / (windowMs / 1000.0);
}
}
package com.argonath.framework.cache;
import com.google.common.cache.*;
import java.util.concurrent.TimeUnit;
/**
* Multi-level cache for quest data.
* L1: In-memory hot cache (very fast, small)
* L2: In-memory warm cache (fast, medium)
* L3: Disk/Database (slower, large)
*/
public class QuestCache {
// L1 Cache: Hot data, very short TTL
private final Cache<String, Quest> hotCache;
// L2 Cache: Warm data, longer TTL
private final Cache<String, Quest> warmCache;
// L3: Database/persistent storage
private final StorageAdapter storage;
public QuestCache(StorageAdapter storage) {
this.storage = storage;
// L1: 100 entries, 10 second TTL
this.hotCache = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.SECONDS)
.recordStats()
.build();
// L2: 1000 entries, 5 minute TTL
this.warmCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.expireAfterAccess(2, TimeUnit.MINUTES)
.recordStats()
.build();
}
/**
* Gets a quest, checking all cache levels.
*/
public Optional<Quest> get(String questId) {
// Check L1 (hot cache)
Quest quest = hotCache.getIfPresent(questId);
if (quest != null) {
return Optional.of(quest);
}
// Check L2 (warm cache)
quest = warmCache.getIfPresent(questId);
if (quest != null) {
// Promote to L1
hotCache.put(questId, quest);
return Optional.of(quest);
}
// Check L3 (storage)
Optional<Quest> loaded = storage.loadQuest(questId);
if (loaded.isPresent()) {
quest = loaded.get();
// Populate both caches
warmCache.put(questId, quest);
hotCache.put(questId, quest);
return Optional.of(quest);
}
return Optional.empty();
}
/**
* Puts a quest in all appropriate cache levels.
*/
public void put(String questId, Quest quest) {
hotCache.put(questId, quest);
warmCache.put(questId, quest);
storage.saveQuest(quest); // Async write-through
}
/**
* Invalidates a quest across all cache levels.
*/
public void invalidate(String questId) {
hotCache.invalidate(questId);
warmCache.invalidate(questId);
}
/**
* Returns combined cache statistics.
*/
public CacheStatistics getStats() {
CacheStats l1Stats = hotCache.stats();
CacheStats l2Stats = warmCache.stats();
return new CacheStatistics(
l1Stats.hitCount() + l2Stats.hitCount(),
l1Stats.missCount() + l2Stats.missCount(),
l1Stats.evictionCount() + l2Stats.evictionCount()
);
}
}
package com.argonath.framework.cache;
/**
* Caches database query results with intelligent invalidation.
*/
public class QueryResultCache {
private final LoadingCache<QueryKey, QueryResult> cache;
private final Map<String, Set<QueryKey>> invalidationMap;
public QueryResultCache(StorageAdapter storage) {
this.invalidationMap = new ConcurrentHashMap<>();
this.cache = CacheBuilder.newBuilder()
.maximumSize(5000)
.expireAfterWrite(30, TimeUnit.SECONDS)
.recordStats()
.build(new CacheLoader<QueryKey, QueryResult>() {
@Override
public QueryResult load(QueryKey key) {
return executeQuery(storage, key);
}
});
}
/**
* Gets query results, using cache when possible.
*/
public QueryResult get(String query, Object... params) {
QueryKey key = new QueryKey(query, params);
try {
return cache.get(key);
} catch (ExecutionException e) {
throw new RuntimeException("Query execution failed", e);
}
}
/**
* Invalidates all queries related to a specific table.
*/
public void invalidateTable(String tableName) {
Set<QueryKey> keys = invalidationMap.get(tableName);
if (keys != null) {
cache.invalidateAll(keys);
keys.clear();
}
}
private QueryResult executeQuery(StorageAdapter storage, QueryKey key) {
QueryResult result = storage.executeQuery(key.query, key.params);
// Track which tables this query touches
Set<String> tables = extractTableNames(key.query);
for (String table : tables) {
invalidationMap.computeIfAbsent(table, k -> ConcurrentHashMap.newKeySet())
.add(key);
}
return result;
}
private static class QueryKey {
final String query;
final Object[] params;
final int hashCode;
QueryKey(String query, Object[] params) {
this.query = query;
this.params = params;
this.hashCode = Objects.hash(query, Arrays.hashCode(params));
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof QueryKey)) return false;
QueryKey other = (QueryKey) obj;
return query.equals(other.query) && Arrays.equals(params, other.params);
}
@Override
public int hashCode() {
return hashCode;
}
}
}
package com.argonath.framework.condition.cache;
/**
* Caches condition evaluation results with smart invalidation.
*/
public class ConditionResultCache {
private final Cache<ConditionCacheKey, Boolean> resultCache;
private final Set<String> invalidationTriggers;
public ConditionResultCache() {
this.invalidationTriggers = ConcurrentHashMap.newKeySet();
this.resultCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(60, TimeUnit.SECONDS)
.recordStats()
.build();
}
/**
* Gets cached result or evaluates condition.
*/
public boolean evaluate(Condition condition, ConditionContext context) {
// Generate cache key
ConditionCacheKey key = buildCacheKey(condition, context);
Boolean cached = resultCache.getIfPresent(key);
if (cached != null) {
return cached;
}
// Cache miss - evaluate
boolean result = condition.evaluate(context);
// Only cache if condition is cacheable
if (isCacheable(condition)) {
resultCache.put(key, result);
}
return result;
}
/**
* Invalidates cache based on event type.
*/
public void invalidateOnEvent(String eventType) {
if (invalidationTriggers.contains(eventType)) {
resultCache.invalidateAll();
}
}
private boolean isCacheable(Condition condition) {
// Don't cache time-based or random conditions
String type = condition.getType();
return !type.contains("time") && !type.contains("random");
}
private ConditionCacheKey buildCacheKey(Condition condition, ConditionContext context) {
return new ConditionCacheKey(
condition.getType(),
condition.serialize().toString(),
context.getPlayer().map(p -> p.getUUID()).orElse(null)
);
}
}
package com.argonath.framework.quest.async;
import java.util.concurrent.*;
/**
* Asynchronous quest evaluation system.
*/
public class AsyncQuestEvaluator {
private final ExecutorService evaluationPool;
private final ScheduledExecutorService schedulerPool;
public AsyncQuestEvaluator(int threadPoolSize) {
// Main evaluation thread pool
this.evaluationPool = new ThreadPoolExecutor(
threadPoolSize,
threadPoolSize * 2,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadFactoryBuilder()
.setNameFormat("quest-eval-%d")
.setPriority(Thread.NORM_PRIORITY)
.build(),
new ThreadPoolExecutor.CallerRunsPolicy() // Backpressure
);
// Scheduler for periodic evaluations
this.schedulerPool = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("quest-scheduler-%d")
.build()
);
}
/**
* Evaluates quest asynchronously.
*/
public CompletableFuture<QuestResult> evaluateAsync(Quest quest, UUID playerId) {
return CompletableFuture.supplyAsync(() -> {
try {
return quest.evaluate(playerId);
} catch (Exception e) {
throw new CompletionException("Quest evaluation failed", e);
}
}, evaluationPool);
}
/**
* Evaluates multiple quests in parallel.
*/
public CompletableFuture<List<QuestResult>> evaluateBatch(
List<Quest> quests,
UUID playerId) {
List<CompletableFuture<QuestResult>> futures = quests.stream()
.map(quest -> evaluateAsync(quest, playerId))
.collect(Collectors.toList());
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
);
}
/**
* Schedules periodic quest evaluation.
*/
public ScheduledFuture<?> schedulePeriodicEvaluation(
Quest quest,
UUID playerId,
long period,
TimeUnit unit) {
return schedulerPool.scheduleAtFixedRate(
() -> {
try {
quest.evaluate(playerId);
} catch (Exception e) {
// Log error but don't stop scheduling
logger.error("Periodic evaluation failed", e);
}
},
0,
period,
unit
);
}
/**
* Shuts down executor pools gracefully.
*/
public void shutdown() {
evaluationPool.shutdown();
schedulerPool.shutdown();
try {
if (!evaluationPool.awaitTermination(30, TimeUnit.SECONDS)) {
evaluationPool.shutdownNow();
}
if (!schedulerPool.awaitTermination(10, TimeUnit.SECONDS)) {
schedulerPool.shutdownNow();
}
} catch (InterruptedException e) {
evaluationPool.shutdownNow();
schedulerPool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
package com.argonath.framework.event.async;
import com.lmax.disruptor.*;
import com.lmax.disruptor.dsl.Disruptor;
/**
* High-performance event processing using LMAX Disruptor.
*/
public class EventProcessingPipeline {
private final Disruptor<ObjectiveEventWrapper> disruptor;
private final RingBuffer<ObjectiveEventWrapper> ringBuffer;
public EventProcessingPipeline(int bufferSize, int handlerThreads) {
// Create disruptor with power-of-2 buffer size
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("event-handler-%d")
.build();
this.disruptor = new Disruptor<>(
ObjectiveEventWrapper::new,
bufferSize,
threadFactory,
ProducerType.MULTI,
new BlockingWaitStrategy()
);
// Set up event handlers
EventHandler<ObjectiveEventWrapper>[] handlers =
createHandlers(handlerThreads);
disruptor.handleEventsWith(handlers);
// Start the disruptor
disruptor.start();
this.ringBuffer = disruptor.getRingBuffer();
}
/**
* Publishes an event to the pipeline.
*/
public void publishEvent(ObjectiveEvent event) {
ringBuffer.publishEvent((wrapper, sequence) -> {
wrapper.event = event;
wrapper.sequence = sequence;
});
}
/**
* Creates event handler array for parallel processing.
*/
private EventHandler<ObjectiveEventWrapper>[] createHandlers(int count) {
@SuppressWarnings("unchecked")
EventHandler<ObjectiveEventWrapper>[] handlers = new EventHandler[count];
for (int i = 0; i < count; i++) {
handlers[i] = new ObjectiveEventHandler();
}
return handlers;
}
/**
* Event handler that processes objective events.
*/
private class ObjectiveEventHandler implements EventHandler<ObjectiveEventWrapper> {
@Override
public void onEvent(ObjectiveEventWrapper wrapper, long sequence, boolean endOfBatch) {
try {
processEvent(wrapper.event);
} catch (Exception e) {
logger.error("Failed to process event", e);
}
}
private void processEvent(ObjectiveEvent event) {
// Update all relevant objectives
QuestManager.getInstance().notifyObjectiveEvent(
event.getPlayerId(),
event
);
}
}
/**
* Event wrapper for disruptor ring buffer.
*/
private static class ObjectiveEventWrapper {
ObjectiveEvent event;
long sequence;
}
public void shutdown() {
disruptor.shutdown();
}
}
package com.argonath.framework.concurrent;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* Lock-free quest progress tracker.
*/
public class LockFreeProgressTracker {
private volatile ProgressNode head;
private static final AtomicReferenceFieldUpdater<LockFreeProgressTracker, ProgressNode> HEAD_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(
LockFreeProgressTracker.class,
ProgressNode.class,
"head"
);
/**
* Updates progress atomically without locks.
*/
public void updateProgress(UUID playerId, String objectiveId, double progress) {
ProgressNode newNode = new ProgressNode(playerId, objectiveId, progress);
ProgressNode currentHead;
do {
currentHead = head;
newNode.next = currentHead;
} while (!HEAD_UPDATER.compareAndSet(this, currentHead, newNode));
}
/**
* Gets current progress for player/objective.
*/
public double getProgress(UUID playerId, String objectiveId) {
ProgressNode current = head;
while (current != null) {
if (current.playerId.equals(playerId) &&
current.objectiveId.equals(objectiveId)) {
return current.progress;
}
current = current.next;
}
return 0.0;
}
private static class ProgressNode {
final UUID playerId;
final String objectiveId;
final double progress;
volatile ProgressNode next;
ProgressNode(UUID playerId, String objectiveId, double progress) {
this.playerId = playerId;
this.objectiveId = objectiveId;
this.progress = progress;
}
}
}
package com.argonath.framework.storage;
import com.zaxxer.hikari.*;
/**
* Optimized database connection pool configuration.
*/
public class DatabaseConnectionPool {
private final HikariDataSource dataSource;
public DatabaseConnectionPool(DatabaseConfig config) {
HikariConfig hikariConfig = new HikariConfig();
// Connection settings
hikariConfig.setJdbcUrl(config.getJdbcUrl());
hikariConfig.setUsername(config.getUsername());
hikariConfig.setPassword(config.getPassword());
// Pool sizing (rule of thumb: connections = cores * 2 + disk spindles)
hikariConfig.setMaximumPoolSize(20);
hikariConfig.setMinimumIdle(5);
// Connection timeout
hikariConfig.setConnectionTimeout(30000); // 30 seconds
hikariConfig.setIdleTimeout(600000); // 10 minutes
hikariConfig.setMaxLifetime(1800000); // 30 minutes
// Performance settings
hikariConfig.setCachePrepStmts(true);
hikariConfig.setPrepStmtCacheSize(250);
hikariConfig.setPrepStmtCacheSqlLimit(2048);
hikariConfig.setUseServerPrepStmts(true);
// Monitoring
hikariConfig.setPoolName("QuestPool");
hikariConfig.setRegisterMbeans(true);
this.dataSource = new HikariDataSource(hikariConfig);
}
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public void shutdown() {
if (dataSource != null && !dataSource.isClosed()) {
dataSource.close();
}
}
/**
* Returns pool statistics.
*/
public PoolStats getStats() {
HikariPoolMXBean poolBean = dataSource.getHikariPoolMXBean();
return new PoolStats(
poolBean.getActiveConnections(),
poolBean.getIdleConnections(),
poolBean.getTotalConnections(),
poolBean.getThreadsAwaitingConnection()
);
}
}
package com.argonath.framework.storage.batch;
/**
* Batch operations for efficient database updates.
*/
public class BatchQuestUpdater {
private final Connection connection;
private final int batchSize;
public BatchQuestUpdater(Connection connection, int batchSize) {
this.connection = connection;
this.batchSize = batchSize;
}
/**
* Updates multiple quest progress entries in batches.
*/
public void updateProgressBatch(List<ProgressUpdate> updates) throws SQLException {
String sql = "UPDATE quest_progress SET current = ?, updated_at = ? " +
"WHERE player_id = ? AND objective_id = ?";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
int count = 0;
for (ProgressUpdate update : updates) {
stmt.setDouble(1, update.progress);
stmt.setLong(2, System.currentTimeMillis());
stmt.setString(3, update.playerId.toString());
stmt.setString(4, update.objectiveId);
stmt.addBatch();
count++;
// Execute batch when size threshold reached
if (count % batchSize == 0) {
stmt.executeBatch();
stmt.clearBatch();
}
}
// Execute remaining
if (count % batchSize != 0) {
stmt.executeBatch();
}
}
}
/**
* Inserts multiple quest completions in a single transaction.
*/
public void insertCompletionsBatch(List<QuestCompletion> completions) throws SQLException {
connection.setAutoCommit(false);
try {
String sql = "INSERT INTO quest_completions " +
"(player_id, quest_id, completed_at, rewards) " +
"VALUES (?, ?, ?, ?)";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
for (QuestCompletion completion : completions) {
stmt.setString(1, completion.playerId.toString());
stmt.setString(2, completion.questId);
stmt.setLong(3, completion.timestamp);
stmt.setString(4, completion.rewardsJson);
stmt.addBatch();
}
stmt.executeBatch();
}
connection.commit();
} catch (SQLException e) {
connection.rollback();
throw e;
} finally {
connection.setAutoCommit(true);
}
}
}
package com.argonath.framework.storage.query;
/**
* Optimized queries for common operations.
*/
public class OptimizedQuestQueries {
/**
* Gets active quests for a player (optimized with covering index).
*/
public static final String GET_ACTIVE_QUESTS =
"SELECT q.id, q.name, qp.progress, qp.updated_at " +
"FROM quests q " +
"INNER JOIN quest_progress qp ON q.id = qp.quest_id " +
"WHERE qp.player_id = ? " +
" AND qp.completed = false " +
" AND qp.progress > 0 " +
"ORDER BY qp.updated_at DESC " +
"LIMIT ?";
/**
* Gets quest progress with objectives (single query instead of N+1).
*/
public static final String GET_QUEST_WITH_OBJECTIVES =
"SELECT " +
" q.id AS quest_id, " +
" q.name AS quest_name, " +
" q.description AS quest_desc, " +
" o.id AS obj_id, " +
" o.type AS obj_type, " +
" op.current AS obj_current, " +
" op.target AS obj_target " +
"FROM quests q " +
"LEFT JOIN objectives o ON q.id = o.quest_id " +
"LEFT JOIN objective_progress op ON o.id = op.objective_id " +
" AND op.player_id = ? " +
"WHERE q.id = ?";
/**
* Bulk query for multiple players' quest states.
*/
public static final String GET_BULK_QUEST_STATES =
"SELECT player_id, quest_id, state, progress " +
"FROM quest_states " +
"WHERE player_id IN (%s) " + // IN clause with placeholders
" AND quest_id = ? " +
" AND updated_at > ?";
/**
* Aggregated quest statistics (uses materialized view for performance).
*/
public static final String GET_QUEST_STATS =
"SELECT * FROM quest_statistics_mv WHERE quest_id = ?";
}
-- Primary indexes for quest tables
-- Quest progress lookup by player
CREATE INDEX idx_quest_progress_player
ON quest_progress(player_id, completed, updated_at);
-- Quest progress lookup by quest
CREATE INDEX idx_quest_progress_quest
ON quest_progress(quest_id, player_id);
-- Objective progress covering index
CREATE INDEX idx_objective_progress_covering
ON objective_progress(player_id, objective_id)
INCLUDE (current, target, updated_at);
-- Quest completions for leaderboards
CREATE INDEX idx_completions_leaderboard
ON quest_completions(quest_id, completed_at DESC);
-- Compound index for active quest queries
CREATE INDEX idx_active_quests
ON quest_progress(player_id, completed, progress)
WHERE completed = false AND progress > 0;
package com.argonath.framework.memory;
import org.apache.commons.pool2.*;
import org.apache.commons.pool2.impl.*;
/**
* Object pool for frequently created quest objects.
*/
public class QuestObjectPool {
private final GenericObjectPool<ConditionContext> contextPool;
private final GenericObjectPool<ObjectiveEvent> eventPool;
public QuestObjectPool() {
// Context pool configuration
GenericObjectPoolConfig<ConditionContext> contextConfig = new GenericObjectPoolConfig<>();
contextConfig.setMaxTotal(1000);
contextConfig.setMaxIdle(100);
contextConfig.setMinIdle(10);
contextConfig.setBlockWhenExhausted(false);
this.contextPool = new GenericObjectPool<>(
new ConditionContextFactory(),
contextConfig
);
// Event pool configuration
GenericObjectPoolConfig<ObjectiveEvent> eventConfig = new GenericObjectPoolConfig<>();
eventConfig.setMaxTotal(5000);
eventConfig.setMaxIdle(500);
eventConfig.setMinIdle(50);
this.eventPool = new GenericObjectPool<>(
new ObjectiveEventFactory(),
eventConfig
);
}
/**
* Borrows a context from the pool.
*/
public ConditionContext borrowContext() throws Exception {
return contextPool.borrowObject();
}
/**
* Returns a context to the pool.
*/
public void returnContext(ConditionContext context) {
contextPool.returnObject(context);
}
/**
* Borrows an event from the pool.
*/
public ObjectiveEvent borrowEvent() throws Exception {
return eventPool.borrowObject();
}
/**
* Returns an event to the pool.
*/
public void returnEvent(ObjectiveEvent event) {
eventPool.returnObject(event);
}
private static class ConditionContextFactory extends BasePooledObjectFactory<ConditionContext> {
@Override
public ConditionContext create() {
return new PooledConditionContext();
}
@Override
public PooledObject<ConditionContext> wrap(ConditionContext obj) {
return new DefaultPooledObject<>(obj);
}
@Override
public void passivateObject(PooledObject<ConditionContext> p) {
// Reset object state before returning to pool
((PooledConditionContext) p.getObject()).reset();
}
}
}
package com.argonath.framework.memory;
import it.unimi.dsi.fastutil.objects.*;
import it.unimi.dsi.fastutil.longs.*;
/**
* Memory-optimized quest progress storage using Fastutil.
*/
public class CompactProgressStorage {
// Use primitive-based maps to reduce memory overhead
private final Object2ObjectOpenHashMap<UUID, Long2DoubleOpenHashMap> playerProgress;
public CompactProgressStorage() {
this.playerProgress = new Object2ObjectOpenHashMap<>();
}
/**
* Stores progress value (uses primitive double, not Double object).
*/
public void setProgress(UUID playerId, long objectiveId, double progress) {
playerProgress.computeIfAbsent(playerId, k -> new Long2DoubleOpenHashMap())
.put(objectiveId, progress);
}
/**
* Gets progress value.
*/
public double getProgress(UUID playerId, long objectiveId) {
Long2DoubleOpenHashMap objectives = playerProgress.get(playerId);
return objectives != null ? objectives.get(objectiveId) : 0.0;
}
/**
* Estimates memory usage in bytes.
*/
public long estimateMemoryUsage() {
long totalBytes = 0;
// HashMap overhead: ~32 bytes per entry + key/value
totalBytes += playerProgress.size() * (32 + 16); // UUID is 16 bytes
// Inner maps
for (Long2DoubleOpenHashMap inner : playerProgress.values()) {
totalBytes += inner.size() * (8 + 8); // long key + double value
}
return totalBytes;
}
}
package com.argonath.framework.memory;
import java.lang.ref.WeakReference;
/**
* Cache that allows garbage collection when memory is low.
*/
public class WeakQuestCache {
private final ConcurrentHashMap<String, WeakReference<Quest>> cache;
private final ReferenceQueue<Quest> referenceQueue;
public WeakQuestCache() {
this.cache = new ConcurrentHashMap<>();
this.referenceQueue = new ReferenceQueue<>();
// Start cleanup thread
startCleanupThread();
}
/**
* Gets quest from cache, may return null if GC'd.
*/
public Optional<Quest> get(String questId) {
WeakReference<Quest> ref = cache.get(questId);
if (ref != null) {
Quest quest = ref.get();
if (quest != null) {
return Optional.of(quest);
} else {
// Reference was cleared, remove from map
cache.remove(questId);
}
}
return Optional.empty();
}
/**
* Puts quest in cache with weak reference.
*/
public void put(String questId, Quest quest) {
cache.put(questId, new WeakReference<>(quest, referenceQueue));
}
/**
* Cleanup thread that removes cleared references.
*/
private void startCleanupThread() {
Thread cleanup = new Thread(() -> {
while (!Thread.interrupted()) {
try {
// Wait for references to be cleared
referenceQueue.remove();
// Remove all cleared references from map
cache.entrySet().removeIf(entry ->
entry.getValue().get() == null
);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "quest-cache-cleanup");
cleanup.setDaemon(true);
cleanup.start();
}
}
package com.argonath.framework.network;
/**
* Batches multiple quest updates into single network packets.
*/
public class QuestUpdateBatcher {
private final Map<UUID, List<QuestUpdate>> pendingUpdates;
private final ScheduledExecutorService scheduler;
private final int maxBatchSize;
private final long flushIntervalMs;
public QuestUpdateBatcher(int maxBatchSize, long flushIntervalMs) {
this.pendingUpdates = new ConcurrentHashMap<>();
this.maxBatchSize = maxBatchSize;
this.flushIntervalMs = flushIntervalMs;
this.scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(
this::flushAll,
flushIntervalMs,
flushIntervalMs,
TimeUnit.MILLISECONDS
);
}
/**
* Queues an update for batching.
*/
public void queueUpdate(UUID playerId, QuestUpdate update) {
List<QuestUpdate> updates = pendingUpdates.computeIfAbsent(
playerId,
k -> new CopyOnWriteArrayList<>()
);
updates.add(update);
// Flush if batch size exceeded
if (updates.size() >= maxBatchSize) {
flush(playerId);
}
}
/**
* Flushes pending updates for a player.
*/
private void flush(UUID playerId) {
List<QuestUpdate> updates = pendingUpdates.remove(playerId);
if (updates != null && !updates.isEmpty()) {
sendBatchedUpdate(playerId, updates);
}
}
/**
* Flushes all pending updates.
*/
private void flushAll() {
Set<UUID> playerIds = new HashSet<>(pendingUpdates.keySet());
for (UUID playerId : playerIds) {
flush(playerId);
}
}
private void sendBatchedUpdate(UUID playerId, List<QuestUpdate> updates) {
// Create batched packet
BatchedQuestUpdatePacket packet = new BatchedQuestUpdatePacket(updates);
// Send to player
NetworkManager.getInstance().sendPacket(playerId, packet);
}
}
/**
* Distributed quest manager for multi-server deployments.
*/
public class DistributedQuestManager {
private final RedisClient redis;
private final String serverId;
/**
* Acquires distributed lock for quest modification.
*/
public boolean acquireQuestLock(String questId, long timeoutMs) {
String lockKey = "quest:lock:" + questId;
return redis.setNX(lockKey, serverId, timeoutMs, TimeUnit.MILLISECONDS);
}
/**
* Publishes quest event to all servers.
*/
public void broadcastQuestEvent(QuestEvent event) {
redis.publish("quest:events", serializeEvent(event));
}
}
/**
* Partitions quest data by player ID for scalability.
*/
public class PartitionedQuestStorage {
private final int partitionCount;
private final StorageAdapter[] partitions;
/**
* Gets partition for player.
*/
private int getPartition(UUID playerId) {
return Math.abs(playerId.hashCode() % partitionCount);
}
/**
* Stores quest progress in appropriate partition.
*/
public void storeProgress(UUID playerId, String questId, double progress) {
int partition = getPartition(playerId);
partitions[partition].storeProgress(playerId, questId, progress);
}
}
/**
* Integration with monitoring systems.
*/
public class QuestMetricsReporter {
private final MeterRegistry registry;
public void recordQuestEvaluation(String questId, long durationNanos) {
Timer.builder("quest.evaluation")
.tag("quest_id", questId)
.register(registry)
.record(durationNanos, TimeUnit.NANOSECONDS);
}
public void recordCacheHit(String cacheType) {
Counter.builder("quest.cache.hit")
.tag("type", cacheType)
.register(registry)
.increment();
}
}
/**
* Quest system performance metrics.
*/
public class QuestPerformanceMetrics {
// Evaluation metrics
public Timer questEvaluationTimer;
public Timer conditionEvaluationTimer;
public Timer objectiveUpdateTimer;
// Throughput metrics
public Counter questStartCounter;
public Counter questCompleteCounter;
public Counter eventProcessedCounter;
// Resource metrics
public Gauge activeQuestsGauge;
public Gauge cacheHitRateGauge;
public Gauge databaseConnectionsGauge;
// Error metrics
public Counter evaluationErrorCounter;
public Counter databaseErrorCounter;
/**
* Generates performance report.
*/
public PerformanceReport generateReport() {
return new PerformanceReport(
questEvaluationTimer.mean(TimeUnit.MILLISECONDS),
eventProcessedCounter.count(),
cacheHitRateGauge.value(),
evaluationErrorCounter.count()
);
}
}
Configuration: 4-core CPU, 8GB RAM, PostgreSQL 14
Test Duration: 60 seconds
Concurrent Players: 100
Operation Throughput Avg Latency P95 Latency P99 Latency
----------------------------------------------------------------------------------------
Quest Evaluation 15,000/sec 0.6ms 1.2ms 2.5ms
Condition Check 50,000/sec 0.2ms 0.4ms 0.8ms
Objective Update 20,000/sec 0.5ms 1.0ms 2.0ms
Quest Completion 5,000/sec 2.0ms 4.0ms 8.0ms
Database Query (cached) 100,000/sec 0.1ms 0.2ms 0.4ms
Database Query (uncached) 10,000/sec 1.0ms 2.5ms 5.0ms
Memory Usage:
- Active quests (1000): ~50MB
- Cache overhead: ~100MB
- Database connections: ~20MB
- Total: ~170MB