summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Vink <mike1994vink@gmail.com>2021-07-29 03:34:50 +0200
committerMike Vink <mike1994vink@gmail.com>2021-07-29 03:34:50 +0200
commit4a354cd7a4edb203dd5e66355dbed0e62994e09b (patch)
treed42d20fb1bc6da7b80ad8c0c94d44b70c5f54fc8
parent9e675c3652eb7a16ce5c2a865c030c76653c921e (diff)
feat(): handshaker part1
-rw-r--r--api/src/main/java/akkamon/api/MessagingEngine.java8
-rw-r--r--client/src/akkamon/client/Client.ts10
-rw-r--r--client/src/akkamon/client/InteractionEngine.ts43
-rw-r--r--client/src/akkamon/scenes/DemoScene.ts12
-rw-r--r--client/src/akkamon/scenes/UIElement.ts47
-rw-r--r--client/src/akkamon/scenes/WorldScene.ts22
-rw-r--r--domain/src/main/java/akkamon/domain/AkkamonMessageEngine.java4
-rw-r--r--domain/src/main/java/akkamon/domain/AkkamonNexus.java32
-rw-r--r--domain/src/main/java/akkamon/domain/InteractionHandshaker.java80
9 files changed, 220 insertions, 38 deletions
diff --git a/api/src/main/java/akkamon/api/MessagingEngine.java b/api/src/main/java/akkamon/api/MessagingEngine.java
index f1c4a78..c315a0b 100644
--- a/api/src/main/java/akkamon/api/MessagingEngine.java
+++ b/api/src/main/java/akkamon/api/MessagingEngine.java
@@ -6,6 +6,7 @@ import akkamon.api.models.*;
import akkamon.domain.AkkamonMessageEngine;
import akkamon.domain.AkkamonNexus;
import akkamon.domain.AkkamonSession;
+import akkamon.domain.InteractionHandshaker;
import com.google.gson.Gson;
import java.util.*;
@@ -16,7 +17,7 @@ public class MessagingEngine implements AkkamonMessageEngine {
private Map<String, Set<AkkamonSession>> sceneIdToAkkamonSessions = new HashMap<>();
private Map<String, AkkamonSession> trainerIdToAkkamonSessions = new HashMap<>();
- private Set<String> pendingInteractioRequestNameSet = new HashSet<>();
+ private Map<String, ActorRef<InteractionHandshaker.Command>> pendingInteractioRequestToHandshaker = new HashMap<>();
private Gson gson = new Gson();
private ActorRef<AkkamonNexus.Command> system;
@@ -65,9 +66,10 @@ public class MessagingEngine implements AkkamonMessageEngine {
}
@Override
- public void broadCastInteractionRequestToSessionWithTrainerIds(List<String> trainerIds, String type, String trainerId, String requestName) {
+ public void broadCastInteractionRequestToSessionWithTrainerIds(List<String> trainerIds, String type, String trainerId, String requestName, ActorRef<InteractionHandshaker.Command> handshaker) {
System.out.println("Sending interaction request " + requestName);
- this.pendingInteractioRequestNameSet.add(requestName);
+ this.pendingInteractioRequestToHandshaker.put(requestName, handshaker);
+ trainerIds.add(trainerId);
for (String id : trainerIds) {
AkkamonSession session = trainerIdToAkkamonSessions.get(id);
if (session != null) {
diff --git a/client/src/akkamon/client/Client.ts b/client/src/akkamon/client/Client.ts
index 2cc5ee8..1591347 100644
--- a/client/src/akkamon/client/Client.ts
+++ b/client/src/akkamon/client/Client.ts
@@ -32,6 +32,7 @@ import {
import type {
IncomingEvent,
+ IncomingInteractionRequest
} from './IncomingEvents';
import {
@@ -91,7 +92,7 @@ export class Client implements AkkamonClient
case EventType.INTERACTION_REQUEST:
console.log("Received an interaction request!");
console.log(event);
- // this.interactionEngine!.push(event);
+ this.interactionEngine!.push(event as IncomingInteractionRequest);
break;
default:
console.log("ignored incoming event, doesn't match EventType interface.");
@@ -116,6 +117,7 @@ export class Client implements AkkamonClient
}
setUIControls(input: Phaser.Input.InputPlugin, menu: any) {
+ console.log("setting ui controls!");
this.controls = new UIControls(input, menu);
}
@@ -201,10 +203,12 @@ export class Client implements AkkamonClient
}
sendInteractionRequest(interaction: Interaction) {
- console.log("sent a battle request!");
+ console.log("sent an interaction request!");
console.log(this.getCurrentSceneKey());
console.log(JSON.stringify(interaction));
- this.interactionEngine!.setAwaitingResponse();
+
+ this.interactionEngine!.setAwaitingInteractionRequestInitiation(true);
+
this.send(new OutgoingInteractionRequestEvent(
this.getCurrentSceneKey(),
interaction
diff --git a/client/src/akkamon/client/InteractionEngine.ts b/client/src/akkamon/client/InteractionEngine.ts
index 92b6b81..cf0c947 100644
--- a/client/src/akkamon/client/InteractionEngine.ts
+++ b/client/src/akkamon/client/InteractionEngine.ts
@@ -4,6 +4,11 @@ import type { WorldScene } from '../scenes/WorldScene';
import { baseQueue } from '../DataWrappers';
+import {
+ InteractionRequestDialogue,
+ WaitingDialogue
+} from '../scenes/UIElement';
+
import type {
IncomingInteractionRequest
} from './IncomingEvents';
@@ -11,18 +16,52 @@ import type {
export class InteractionEngine extends AkkamonEngine {
+ private scene: WorldScene;
+
private messageQueue = baseQueue();
+ private awaitingInit: boolean = false;
+
+ private waitingForResponseOf: String | undefined;
+
+ private requestBackLog = baseQueue<IncomingInteractionRequest>();
+
+ private answering: boolean = false;
+
constructor(scene: WorldScene) {
super();
+ this.scene = scene;
+ }
+
+ playerIsBusy() {
+ return this.waitingForResponseOf || this.awaitingInit || this.answering
}
update() {
+ if (!this.requestBackLog.isEmpty()
+ && !this.playerIsBusy()
+ && this.scene.menus.isEmpty()) {
+ let message = this.requestBackLog.pop();
+ this.scene.pushMenu(new InteractionRequestDialogue(this.scene, ["YES", "NO"], {name: message!.trainerId, requestType: message!.type}));
+ }
}
- setAwaitingResponse() {
+ setAwaitingInteractionRequestInitiation(value: boolean) {
+ this.awaitingInit = value;
}
- push() {
+ push(event: IncomingInteractionRequest) {
+ // check trainerId
+ if (this.awaitingInit) {
+ this.waitingForResponseOf = event.requestName;
+ this.scene.clearMenus();
+
+ this.scene.pushMenu(new WaitingDialogue(this.scene, new Phaser.GameObjects.Group(this.scene), 20, 'Awaiting player response...'));
+
+ this.awaitingInit = false;
+ } else {
+ this.requestBackLog.push(event);
+ }
}
+
}
diff --git a/client/src/akkamon/scenes/DemoScene.ts b/client/src/akkamon/scenes/DemoScene.ts
index 9544df3..b6b94e5 100644
--- a/client/src/akkamon/scenes/DemoScene.ts
+++ b/client/src/akkamon/scenes/DemoScene.ts
@@ -1,5 +1,7 @@
import Phaser from 'phaser';
+import { client } from '../../app';
+
import type {
BasePhaserScene
} from '../PhaserTypes';
@@ -9,6 +11,14 @@ import {
createWorldScene
} from './WorldScene';
-let DemoScene = createWorldScene(Phaser.Scene, "DemoScene", "map", "akkamon-demo-extruded");
+function updatable<Scene extends BasePhaserScene>(scene: Scene) {
+ return class DemoScene extends scene {
+ update(time: number, delta: number) {
+ client.updateScene(delta);
+ }
+ }
+}
+
+let DemoScene = updatable(createWorldScene(Phaser.Scene, "DemoScene", "map", "akkamon-demo-extruded"));
export default DemoScene;
diff --git a/client/src/akkamon/scenes/UIElement.ts b/client/src/akkamon/scenes/UIElement.ts
index 03cce88..f1d5dd3 100644
--- a/client/src/akkamon/scenes/UIElement.ts
+++ b/client/src/akkamon/scenes/UIElement.ts
@@ -24,7 +24,9 @@ class MenuText extends Phaser.GameObjects.Text {
scene.add.existing(this);
group.add(this);
this.setDepth(groupDepth);
+
}
+
}
class WrappedMenuText extends MenuText {
@@ -67,6 +69,7 @@ export interface AkkamonMenu {
destroyAndGoBack: () => void
confirm: () => void
destroyGroup: () => void
+ setMenuVisible: (v: boolean) => void
}
@@ -91,6 +94,10 @@ class Menu extends Phaser.GameObjects.Image implements AkkamonMenu {
xOffsetFromRight?: number
pickerOffset?:number
+ setMenuVisible(value: boolean) {
+ this.group!.setVisible(value);
+ }
+
destroyGroup() {
this.group!.destroy(true);
}
@@ -297,7 +304,7 @@ class ConfirmationDialogue extends Menu implements AkkamonMenu {
options?: Array<string>
dialogueBox?: Dialogue
- constructor(scene: WorldScene, options: Array<string>, dialogueData: {[key: string]: string}) {
+ constructor(scene: WorldScene, options: Array<string>, dialogueData: {[key: string]: string} | string) {
super(scene, "confirmation-dialogue");
let camera = scene.cameras.main;
this.setDisplaySize(200, 0.83 * 200)
@@ -347,6 +354,10 @@ class Dialogue extends Phaser.GameObjects.Image implements AkkamonMenu {
);
}
+ setMenuVisible(value: boolean) {
+ this.group!.setVisible(value);
+ }
+
selectButton() {
}
confirm() {
@@ -416,7 +427,7 @@ class ChallengeDialogue extends ConfirmationDialogue implements AkkamonMenu {
if (this.buttons![this.index!].text === "YES") {
this.akkamonScene.requestBattle(this.challengedTrainerName);
this.akkamonScene.clearMenus();
- this.akkamonScene.pushMenu(new WaitingDialogue(this.akkamonScene, new Phaser.GameObjects.Group(this.scene), 20));
+ this.akkamonScene.pushMenu(new WaitingDialogue(this.akkamonScene, new Phaser.GameObjects.Group(this.scene), 20, 'Awaiting request initialisation...'));
} else {
this.destroyAndGoBack();
}
@@ -428,14 +439,37 @@ class ChallengeDialogue extends ConfirmationDialogue implements AkkamonMenu {
}
}
-class WaitingDialogue extends Dialogue {
+export class InteractionRequestDialogue extends ConfirmationDialogue implements AkkamonMenu {
+
+ constructor(scene: WorldScene, options: Array<string>, dialogueData: {name: string, requestType: string}) {
+ super(scene, options, dialogueData);
+ this.dialogueBox!.push(
+ `Do you want to ${dialogueData.requestType} with ${dialogueData.name}?`
+ );
+ this.dialogueBox!.displayNextDialogue();
+ }
+
+ confirm() {
+ if (this.buttons![this.index!].text === "YES") {
+ } else {
+ this.destroyAndGoBack();
+ }
+ }
+
+ destroy() {
+ this.scene.time.removeAllEvents();
+ super.destroy();
+ }
+}
+
+export class WaitingDialogue extends Dialogue {
waitingPrinter: any
- constructor(scene: WorldScene, group: Phaser.GameObjects.Group, depth: number) {
+ constructor(scene: WorldScene, group: Phaser.GameObjects.Group, depth: number, text: string) {
super(scene, group, depth);
- this.typewriteText("Waiting on reponse...");
+ this.typewriteText(text);
this.waitingPrinter = setInterval(() => {
this.displayedText.text = '';
- this.typewriteText("Waiting on reponse...");
+ this.typewriteText(text);
}, 3000);
}
@@ -449,4 +483,5 @@ class WaitingDialogue extends Dialogue {
this.scene.time.removeAllEvents();
super.destroy();
}
+
}
diff --git a/client/src/akkamon/scenes/WorldScene.ts b/client/src/akkamon/scenes/WorldScene.ts
index fd866aa..3e8db40 100644
--- a/client/src/akkamon/scenes/WorldScene.ts
+++ b/client/src/akkamon/scenes/WorldScene.ts
@@ -17,8 +17,6 @@ export let TILE_SIZE = 32;
export interface WorldScene extends Phaser.Scene {
- client: typeof client;
-
map?: Phaser.Tilemaps.Tilemap
spawnPoint?: Phaser.Types.Tilemaps.TiledObject;
spawnPointTilePos?: {
@@ -47,15 +45,14 @@ export interface WorldScene extends Phaser.Scene {
requestBattle: (remotePlayerData: string | string[]) => void
clearMenus: () => void
+
}
export function createWorldScene<PhaserScene extends BasePhaserScene>(scene: PhaserScene, sceneKey: string, mapKey: string, tileSetKey: string) {
- return class WorldScene extends scene implements WorldScene {
+ return class WorldScene extends scene {
map?: Phaser.Tilemaps.Tilemap;
- client = client;
-
menus = baseStack<AkkamonMenu>();
spawnPoint?: Phaser.Types.Tilemaps.TiledObject;
@@ -90,7 +87,7 @@ export function createWorldScene<PhaserScene extends BasePhaserScene>(scene: Pha
);
this.spawnPointTilePos = tilePos;
- this.client.requestInitWorldScene(
+ client.requestInitWorldScene(
this
);
@@ -104,11 +101,11 @@ export function createWorldScene<PhaserScene extends BasePhaserScene>(scene: Pha
}
menuTakesUIControl(input: Phaser.Input.InputPlugin, menu: AkkamonMenu): void {
- this.client.setUIControls(input, menu);
+ client.setUIControls(input, menu);
}
isUsingGridControls() {
- this.client.setGridControls();
+ client.setGridControls();
}
pushMenu(menu: AkkamonMenu) {
@@ -126,6 +123,7 @@ export function createWorldScene<PhaserScene extends BasePhaserScene>(scene: Pha
this.popMenu();
if (!this.menus.isEmpty()) {
this.menuTakesUIControl(this.input, this.menus.peek()!);
+ this.menus.peek()!.setMenuVisible(true);
} else {
this.isUsingGridControls();
}
@@ -134,11 +132,11 @@ export function createWorldScene<PhaserScene extends BasePhaserScene>(scene: Pha
}
getPlayerPixelPosition(): Phaser.Math.Vector2 {
- return this.client.requestPlayerPixelPosition();
+ return client.requestPlayerPixelPosition();
}
getRemotePlayerNames(): Array<string> {
- let remotePlayerData = this.client.requestRemotePlayerData();
+ let remotePlayerData = client.requestRemotePlayerData();
if (remotePlayerData.size === 0) {
return ['Nobody Online'];
} else {
@@ -147,9 +145,9 @@ export function createWorldScene<PhaserScene extends BasePhaserScene>(scene: Pha
}
requestBattle(remotePlayerName: string | string[]): void {
- this.client.sendInteractionRequest({
+ client.sendInteractionRequest({
type: "battle",
- requestingTrainerId: this.client.getSessionTrainerId()!,
+ requestingTrainerId: client.getSessionTrainerId()!,
receivingTrainerIds: Array.isArray(remotePlayerName) ? remotePlayerName : [remotePlayerName]
});
}
diff --git a/domain/src/main/java/akkamon/domain/AkkamonMessageEngine.java b/domain/src/main/java/akkamon/domain/AkkamonMessageEngine.java
index b3ab9b2..075726d 100644
--- a/domain/src/main/java/akkamon/domain/AkkamonMessageEngine.java
+++ b/domain/src/main/java/akkamon/domain/AkkamonMessageEngine.java
@@ -1,5 +1,7 @@
package akkamon.domain;
+import akka.actor.typed.ActorRef;
+
import java.util.List;
import java.util.Map;
@@ -7,7 +9,7 @@ public interface AkkamonMessageEngine {
// broadcasts position info to WebSocket Clients
void broadCastHeartBeatToScene(String sceneId, Map<String, AkkamonNexus.MovementQueueReading> trainerPositions);
- void broadCastInteractionRequestToSessionWithTrainerIds(List<String> trainerIds, String type, String trainerId, String requestName);
+ void broadCastInteractionRequestToSessionWithTrainerIds(List<String> trainerIds, String type, String trainerId, String requestName, ActorRef<InteractionHandshaker.Command> handshaker);
void registerTrainerSessionToSceneAndTrainerIdMaps(String sceneId, AkkamonSession session);
diff --git a/domain/src/main/java/akkamon/domain/AkkamonNexus.java b/domain/src/main/java/akkamon/domain/AkkamonNexus.java
index 003630f..9099617 100644
--- a/domain/src/main/java/akkamon/domain/AkkamonNexus.java
+++ b/domain/src/main/java/akkamon/domain/AkkamonNexus.java
@@ -15,6 +15,22 @@ public class AkkamonNexus extends AbstractBehavior<AkkamonNexus.Command> {
public interface Command {}
+
+ public static class RespondInteractionHandshaker implements Command {
+ public String requestName;
+ public boolean allRepliedInTime;
+
+ public RespondInteractionHandshaker(String requestName, boolean allRepliedInTime) {
+ this.requestName = requestName;
+ this.allRepliedInTime = allRepliedInTime;
+ }
+
+ }
+
+ public static class InteractionReply implements Command {
+ public String trainerId;
+ }
+
public static class RequestInteraction
implements Command {
@@ -239,24 +255,32 @@ public class AkkamonNexus extends AbstractBehavior<AkkamonNexus.Command> {
.onMessage(RequestStopMoving.class, this::onStopMoving)
.onMessage(RequestNewTilePos.class, this::onNewTilePos)
.onMessage(RequestInteraction.class, this::onInteractionRequest)
+ .onMessage(RespondInteractionHandshaker.class, this::onInteractionHandshakerResponse)
.build();
}
+ private Behavior<Command> onInteractionHandshakerResponse(RespondInteractionHandshaker r) {
+
+ return this;
+ }
+
private AkkamonNexus onInteractionRequest(RequestInteraction interactionRequest) {
List<String> needConfirmation = interactionRequest.forwardTo;
getContext().getLog().info("Creating interactionHandshaker of type {} from {} to {} ", interactionRequest.type, interactionRequest.trainerId, interactionRequest.forwardTo);
String requestName = "interaction-handshaker-" + interactionRequest.type + "-" + interactionRequest.trainerId + "-" + interactionRequest.requestId;
- getContext().spawn(InteractionHandshaker.create(
+
+ ActorRef<InteractionHandshaker.Command> handshaker = getContext().spawn(InteractionHandshaker.create(
interactionRequest.trainerId,
+ interactionRequest.type,
interactionRequest.forwardTo,
- interactionRequest.requestId,
+ requestName,
interactionRequest.replyTo,
- Duration.ofSeconds(20)
+ Duration.ofSeconds(60)
), requestName);
- messageEngine.broadCastInteractionRequestToSessionWithTrainerIds(needConfirmation, interactionRequest.type, interactionRequest.trainerId, requestName);
+ messageEngine.broadCastInteractionRequestToSessionWithTrainerIds(needConfirmation, interactionRequest.type, interactionRequest.trainerId, requestName, handshaker);
return this;
}
diff --git a/domain/src/main/java/akkamon/domain/InteractionHandshaker.java b/domain/src/main/java/akkamon/domain/InteractionHandshaker.java
index d6c35ea..6f47282 100644
--- a/domain/src/main/java/akkamon/domain/InteractionHandshaker.java
+++ b/domain/src/main/java/akkamon/domain/InteractionHandshaker.java
@@ -1,6 +1,5 @@
package akkamon.domain;
-import akka.actor.Timers;
import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.*;
@@ -8,6 +7,7 @@ import akka.actor.typed.javadsl.*;
import java.time.Duration;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
public class InteractionHandshaker extends AbstractBehavior<InteractionHandshaker.Command> {
@@ -15,11 +15,27 @@ public class InteractionHandshaker extends AbstractBehavior<InteractionHandshake
public interface Command {
}
+ public static enum HandshakeTimeout implements Command {
+ INSTANCE
+ }
+
+ static class WrappedReply implements Command {
+
+ final AkkamonNexus.InteractionReply reply;
+
+ WrappedReply(
+ AkkamonNexus.InteractionReply reply
+ ) {
+ this.reply = reply;
+ }
+
+ }
public static Behavior<Command> create(
String trainerId,
+ String type,
List<String> needingConfirmation,
- long requestId,
+ String requestName,
ActorRef<AkkamonNexus.Command> replyTo,
Duration timeout) {
@@ -28,8 +44,9 @@ public class InteractionHandshaker extends AbstractBehavior<InteractionHandshake
timers -> new InteractionHandshaker(
context,
trainerId,
+ type,
new HashSet(needingConfirmation),
- requestId,
+ requestName,
replyTo,
timeout,
timers
@@ -37,19 +54,70 @@ public class InteractionHandshaker extends AbstractBehavior<InteractionHandshake
));
}
+ private Set<String> stillWaiting;
+
+ private ActorRef<AkkamonNexus.Command> replyTo;
+
+ private String requestName;
+ private String type;
+
+ private Set<String> alreadyWaitingForInteraction = new HashSet<>();
+
public InteractionHandshaker(ActorContext<Command> context,
String trainerId,
- Set<String> needingConfirmation,
- long requestId,
+ String type,
+ Set<String> needingToShakeHands,
+ String requestName,
ActorRef<AkkamonNexus.Command> replyTo,
Duration timeout,
TimerScheduler<Command> timers) {
+
super(context);
+
+ timers.startSingleTimer(HandshakeTimeout.INSTANCE, timeout);
+
+ this.replyTo = replyTo;
+ this.requestName = requestName;
+ this.type = type;
+
+ ActorRef<AkkamonNexus.InteractionReply> respondTrainerPositionAdapter = context.messageAdapter(AkkamonNexus.InteractionReply.class, WrappedReply::new);
+
+
+ stillWaiting = needingToShakeHands;
}
@Override
public Receive<Command> createReceive() {
- return null;
+ return newReceiveBuilder()
+ .onMessage(WrappedReply.class, this::onReply)
+ .onMessage(HandshakeTimeout.class, this::onHandshakeTimeOut)
+ .build();
+ }
+
+ private Behavior<Command> onHandshakeTimeOut(HandshakeTimeout timeoutInstance) {
+ getContext().getLog().info("Received {}", timeoutInstance);
+ replyTo.tell(
+ new AkkamonNexus.RespondInteractionHandshaker(
+ requestName,
+ false
+ )
+ );
+ return Behaviors.stopped();
+ }
+
+
+ private Behavior<Command> onReply(WrappedReply w) {
+ getContext().getLog().info("received reply from {}!", w.reply.trainerId);
+ return respondIfAllRepliesReceived();
+ }
+
+ private Behavior<Command> respondIfAllRepliesReceived() {
+ if (this.stillWaiting.isEmpty()) {
+ // send response
+ return Behaviors.stopped();
+ } else {
+ return this;
+ }
}
}