Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.bencodez.advancedcore.core.reward;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/** Platform-neutral identity and placeholder state for one logical reward execution. */
public final class SharedRewardContext {
private final UUID userId;
private final String playerName;
private final HashMap<String, String> placeholders;

public SharedRewardContext(UUID userId, String playerName, Map<String, String> placeholders) {
this.userId = Objects.requireNonNull(userId, "userId");
this.playerName = playerName;
this.placeholders = new HashMap<>();
if (placeholders != null) {
this.placeholders.putAll(placeholders);
}
}

public UUID userId() {
return userId;
}

public String playerName() {
return playerName;
}

/** Mutable execution-local placeholders; durable adapters decide when to persist them. */
public HashMap<String, String> placeholders() {
return placeholders;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.bencodez.advancedcore.core.reward;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

/**
* Adapter over the existing replay owner, scoped to ONE logical reward occurrence.
* That owner serializes execution/retries of each path; the orchestrator allocates
* no second queue, replay store or lock registry. Snapshot reads must not block the
* platform thread. Write stages complete only after persistence, never submission.
*/
public interface SharedRewardDurability {
SharedRewardDurability NONE = new SharedRewardDurability() {
@Override public int completedSteps(String executionPath) { return 0; }
@Override public CompletionStage<Void> checkpoint(String executionPath, int completedSteps,
SharedRewardContext context) { return CompletableFuture.completedFuture(null); }
@Override public CompletionStage<Void> defer(String executionPath, int nextStep,
SharedRewardContext context) { return CompletableFuture.failedFuture(
new IllegalStateException("Offline reward deferral is unavailable")); }
@Override public boolean durable() { return false; }
};

/** Existing API retained; an unversioned nonzero cursor is not safe to resume. */
int completedSteps(String executionPath);

/** Already-loaded snapshot, or null only when this occurrence/path has never begun. */
default SharedRewardProgress loadProgress(String executionPath) {
if (durable()) throw new UnsupportedOperationException("Durable replay must load versioned decision/progress state");
return null;
}

/**
* Atomically persist the initial decision, fingerprint and placeholders at cursor
* zero, or return the already-persisted state. Never overwrite an earlier decision.
* A lost acknowledgement must still be recoverable by loadProgress on retry.
*/
default CompletionStage<SharedRewardProgress> begin(String executionPath, SharedRewardProgress proposed) {
if (durable()) return CompletableFuture.failedFuture(
new UnsupportedOperationException("Durable replay must persist the initial execution decision"));
return CompletableFuture.completedFuture(proposed);
}

/** Completes only after progress and placeholder state are durable. */
CompletionStage<Void> checkpoint(String executionPath, int completedSteps, SharedRewardContext context);

/** Bound form used by the shared orchestrator; adapters persist the same binding with the cursor. */
default CompletionStage<Void> checkpoint(String executionPath, String fingerprint, int completedSteps,
SharedRewardContext context) {
requireFingerprint(executionPath, fingerprint);
return checkpoint(executionPath, completedSteps, context);
}

/** Completes only after the still-pending reward occurrence is durable. */
CompletionStage<Void> defer(String executionPath, int nextStep, SharedRewardContext context);

default CompletionStage<Void> defer(String executionPath, String fingerprint, int nextStep,
SharedRewardContext context) {
requireFingerprint(executionPath, fingerprint);
return defer(executionPath, nextStep, context);
}

private void requireFingerprint(String executionPath, String fingerprint) {
if (!durable()) return;
SharedRewardProgress state = loadProgress(executionPath);
if (state == null || !state.planFingerprint().equals(fingerprint)) {
throw new IllegalStateException("Reward checkpoint belongs to a different or unbound plan: " + executionPath);
}
}

boolean durable();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package com.bencodez.advancedcore.core.reward;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

import com.bencodez.advancedcore.core.reward.SharedRewardRequirement.Outcome;

/**
* Sequences already-configured reward work without Bukkit dependencies. Native
* actions remain in platform adapters, while durability remains owned by the
* injected replay adapter.
*/
public final class SharedRewardOrchestrator {
private final SharedRewardPlatform platform;

public SharedRewardOrchestrator(SharedRewardPlatform platform) {
this.platform = Objects.requireNonNull(platform, "platform");
}

public CompletionStage<SharedRewardResult> execute(SharedRewardPlan plan, SharedRewardContext context,
SharedRewardDurability durability) {
Objects.requireNonNull(plan, "plan");
Objects.requireNonNull(context, "context");
SharedRewardDurability replay = durability == null ? SharedRewardDurability.NONE : durability;
return execute(plan, context, replay, pathSegment(plan.id()));
}

public CompletionStage<SharedRewardResult> executeNested(SharedRewardPlan plan, SharedRewardContext context,
SharedRewardDurability durability, String parentPath) {
Objects.requireNonNull(plan, "plan");
Objects.requireNonNull(context, "context");
Objects.requireNonNull(parentPath, "parentPath");
String segment = pathSegment(plan.id());
String path = parentPath.isBlank() ? segment : parentPath + "/" + segment;
return execute(plan, context, durability == null ? SharedRewardDurability.NONE : durability, path);
}

private CompletionStage<SharedRewardResult> execute(SharedRewardPlan plan, SharedRewardContext context,
SharedRewardDurability durability, String executionPath) {
try {
if (platform.isShuttingDown()) return failed("Reward platform is shutting down");
String fingerprint = durability.durable() ? plan.fingerprint() : "non-durable";
SharedRewardProgress saved = durability.durable() ? durability.loadProgress(executionPath) : null;
if (saved != null) {
validateProgress(plan, fingerprint, saved, executionPath);
return continueFromProgress(plan, context, durability, executionPath, fingerprint, saved);
}

int legacyCursor = durability.completedSteps(executionPath);
if (legacyCursor < 0 || legacyCursor > plan.steps().size()) {
return failed("Invalid durable reward cursor " + legacyCursor + " for " + executionPath);
}
if (durability.durable() && legacyCursor != 0) {
return failed("Cannot resume an unversioned reward cursor: " + executionPath);
}

CompletionStage<Outcome> requirements = legacyCursor > 0
? CompletableFuture.completedFuture(Outcome.PASS)
: evaluateRequirements(plan.requirements(), context);
return requirements.thenCompose(outcome -> {
if (outcome == Outcome.RETRY) {
if (!durability.durable()) return failed("Retryable reward requirement needs durable deferral: " + executionPath);
CompletionStage<Void> deferred;
try {
deferred = durability.defer(executionPath, legacyCursor, context);
if (deferred == null) return CompletableFuture.failedFuture(
new IllegalStateException("Reward durability adapter returned null requirement deferral stage"));
} catch (Throwable failure) {
return CompletableFuture.failedFuture(failure);
}
return deferred.thenApply(ignored -> SharedRewardResult.DEFERRED);
}

boolean passed = outcome == Outcome.PASS
&& (plan.chance() >= 1.0 || platform.nextChanceRoll() < plan.chance());
SharedRewardProgress decision = new SharedRewardProgress(fingerprint, passed, legacyCursor,
context.placeholders(), passed ? platform.now().plus(plan.delay()) : platform.now());
CompletionStage<SharedRewardProgress> persisted;
try {
persisted = durability.begin(executionPath, decision);
if (persisted == null) return CompletableFuture.failedFuture(
new IllegalStateException("Reward durability adapter returned null begin stage"));
} catch (Throwable failure) {
return CompletableFuture.failedFuture(failure);
}
return persisted.thenCompose(progress -> {
validateProgress(plan, fingerprint, progress, executionPath);
return continueFromProgress(plan, context, durability, executionPath, fingerprint, progress);
});
});
} catch (Throwable failure) {
return CompletableFuture.failedFuture(failure);
}
}

private CompletionStage<SharedRewardResult> continueFromProgress(SharedRewardPlan plan, SharedRewardContext context,
SharedRewardDurability durability, String executionPath, String fingerprint, SharedRewardProgress progress) {
context.placeholders().clear();
context.placeholders().putAll(progress.placeholders());
if (!progress.eligible()) return CompletableFuture.completedFuture(SharedRewardResult.NOT_ELIGIBLE);
return executeEligible(plan, context, durability, executionPath, fingerprint, progress);
}

private void validateProgress(SharedRewardPlan plan, String fingerprint, SharedRewardProgress progress,
String executionPath) {
if (progress == null || !fingerprint.equals(progress.planFingerprint())) {
throw new IllegalStateException("Reward plan changed or has no durable binding: " + executionPath);
}
if (progress.eligible() && progress.completedSteps() == 0 && !plan.delay().isZero()
&& progress.notBefore() == null) {
throw new IllegalStateException("Durable reward progress has no delay deadline: " + executionPath);
}
if (progress.completedSteps() > plan.steps().size()) {
throw new IllegalStateException("Invalid durable reward cursor for " + executionPath);
}
}

private CompletionStage<SharedRewardResult> executeEligible(SharedRewardPlan plan, SharedRewardContext context,
SharedRewardDurability durability, String executionPath, String fingerprint, SharedRewardProgress progress) {
int resume = progress.completedSteps();
java.util.function.Supplier<CompletionStage<SharedRewardResult>> work =
() -> executeSteps(plan, context, durability, executionPath, fingerprint, resume);
Duration delay = Duration.ZERO;
if (resume == 0 && !plan.delay().isZero()) {
delay = durability.durable() ? Duration.between(platform.now(), progress.notBefore()) : plan.delay();
if (delay.isNegative()) delay = Duration.ZERO;
}
if (delay.isZero()) return work.get();
try {
CompletionStage<SharedRewardResult> delayed = platform.delay(delay, work);
return delayed == null ? failed("Reward platform returned null delay stage") : delayed;
} catch (Throwable failure) {
return CompletableFuture.failedFuture(failure);
}
}

private CompletionStage<Outcome> evaluateRequirements(List<SharedRewardRequirement> requirements,
SharedRewardContext context) {
CompletionStage<Outcome> chain = CompletableFuture.completedFuture(Outcome.PASS);
for (SharedRewardRequirement requirement : requirements) {
chain = chain.thenCompose(previous -> {
if (previous != Outcome.PASS) return CompletableFuture.completedFuture(previous);
try {
CompletionStage<Outcome> stage = requirement.evaluate(context);
return stage == null ? CompletableFuture.failedFuture(
new IllegalStateException("Reward requirement returned null completion stage")) : stage;
} catch (Throwable failure) {
return CompletableFuture.failedFuture(failure);
}
});
}
return chain.thenApply(outcome -> outcome == null ? Outcome.FAIL : outcome);
}

private CompletionStage<SharedRewardResult> executeSteps(SharedRewardPlan plan, SharedRewardContext context,
SharedRewardDurability durability, String executionPath, String fingerprint, int index) {
CompletionStage<SharedRewardResult> chain = CompletableFuture.completedFuture(SharedRewardResult.COMPLETED);
for (int current = index; current < plan.steps().size(); current++) {
final int stepIndex = current;
chain = chain.thenCompose(previous -> previous == SharedRewardResult.DEFERRED
? CompletableFuture.completedFuture(SharedRewardResult.DEFERRED)
: executeStep(plan.steps().get(stepIndex), context, durability, executionPath, fingerprint, stepIndex));
}
return chain.thenCompose(result -> result != SharedRewardResult.DEFERRED && platform.isShuttingDown()
? failed("Reward platform shut down before execution completed")
: CompletableFuture.completedFuture(result));
}

private CompletionStage<SharedRewardResult> executeStep(SharedRewardStep step, SharedRewardContext context,
SharedRewardDurability durability, String executionPath, String fingerprint, int index) {
if (platform.isShuttingDown()) return failed("Reward platform shut down before execution completed");
if (step.requiresOnlinePlayer() && !platform.isOnline(context.userId())) {
if (!durability.durable()) return failed("Player became unavailable during non-durable reward step " + step.id());
CompletionStage<Void> deferred;
try {
deferred = durability.defer(executionPath, fingerprint, index, context);
if (deferred == null) return CompletableFuture.failedFuture(
new IllegalStateException("Reward durability adapter returned null deferral stage"));
} catch (Throwable failure) {
return CompletableFuture.failedFuture(failure);
}
return deferred.thenApply(ignored -> SharedRewardResult.DEFERRED);
}

String stepPath = executionPath + "/" + pathSegment(step.id()) + ":" + index;
CompletionStage<SharedRewardResult> action;
try {
action = step.action().execute(context, stepPath);
if (action == null) return CompletableFuture.failedFuture(
new IllegalStateException("Reward step returned null completion stage: " + step.id()));
} catch (Throwable failure) {
return CompletableFuture.failedFuture(failure);
}

return action.thenCompose(result -> {
if (result == null) return CompletableFuture.failedFuture(
new IllegalStateException("Reward step returned null result: " + step.id()));
if (result == SharedRewardResult.DEFERRED) return CompletableFuture.completedFuture(SharedRewardResult.DEFERRED);
CompletionStage<Void> checkpoint;
try {
checkpoint = durability.checkpoint(executionPath, fingerprint, index + 1, context);
if (checkpoint == null) return CompletableFuture.failedFuture(
new IllegalStateException("Reward durability adapter returned null checkpoint stage"));
} catch (Throwable failure) {
return CompletableFuture.failedFuture(failure);
}
return checkpoint.thenApply(ignored -> SharedRewardResult.COMPLETED);
});
}

private static String pathSegment(String id) {
if (id.indexOf('/') < 0 && id.indexOf(':') < 0 && id.indexOf('%') < 0) return id;
return "%" + Base64.getUrlEncoder().withoutPadding().encodeToString(id.getBytes(StandardCharsets.UTF_8));
}

private CompletionStage<SharedRewardResult> failed(String message) {
return CompletableFuture.failedFuture(new IllegalStateException(message));
}
}
Loading
Loading