From c0004768de6695e2acfa48c392765b03b81ba9d0 Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Thu, 29 Jul 2021 17:37:06 +0200 Subject: refactor(): making battles is now easy --- api/src/main/java/akkamon/api/EventSocket.java | 11 +- api/src/main/java/akkamon/api/MessagingEngine.java | 131 +++--- api/src/main/java/akkamon/api/models/Event.java | 4 +- .../java/akkamon/api/models/HeartBeatEvent.java | 6 +- .../main/java/akkamon/api/models/Interaction.java | 15 +- .../akkamon/api/models/InteractionRequest.java | 13 - .../api/models/OutgoingInteractionRequest.java | 15 + .../api/models/TrainerRegistrationReplyEvent.java | 9 +- client/src/akkamon/client/Client.ts | 48 ++- client/src/akkamon/client/IncomingEvents.ts | 9 +- client/src/akkamon/client/InteractionEngine.ts | 4 +- client/src/akkamon/client/OutgoingEvents.ts | 42 +- client/src/akkamon/client/Session.ts | 12 +- client/src/akkamon/client/Socket.ts | 2 +- client/src/akkamon/render/engine/GridPhysics.ts | 18 +- .../akkamon/render/engine/RemotePlayerEngine.ts | 22 +- client/src/akkamon/scenes/AkkamonWorldScene.ts | 8 +- client/src/akkamon/scenes/UIElement.ts | 98 +++-- client/src/akkamon/scenes/WorldScene.ts | 22 +- client/src/app.ts | 4 +- .../java/akkamon/domain/AkkamonMessageEngine.java | 13 +- .../src/main/java/akkamon/domain/AkkamonNexus.java | 406 ------------------- .../main/java/akkamon/domain/AkkamonSession.java | 6 +- .../main/java/akkamon/domain/HeartBeatQuery.java | 38 +- .../java/akkamon/domain/InteractionHandshaker.java | 46 ++- .../main/java/akkamon/domain/InteractionTypes.java | 5 + .../java/akkamon/domain/SceneTrainerGroup.java | 239 ----------- domain/src/main/java/akkamon/domain/Trainer.java | 126 ------ .../java/akkamon/domain/actors/AkkamonBattle.java | 36 ++ .../java/akkamon/domain/actors/AkkamonNexus.java | 447 +++++++++++++++++++++ .../akkamon/domain/actors/SceneTrainerGroup.java | 243 +++++++++++ .../main/java/akkamon/domain/actors/Trainer.java | 128 ++++++ .../test/java/akkamon/domain/AkkamonNexusTest.java | 2 + 33 files changed, 1201 insertions(+), 1027 deletions(-) delete mode 100644 api/src/main/java/akkamon/api/models/InteractionRequest.java create mode 100644 api/src/main/java/akkamon/api/models/OutgoingInteractionRequest.java delete mode 100644 domain/src/main/java/akkamon/domain/AkkamonNexus.java create mode 100644 domain/src/main/java/akkamon/domain/InteractionTypes.java delete mode 100644 domain/src/main/java/akkamon/domain/SceneTrainerGroup.java delete mode 100644 domain/src/main/java/akkamon/domain/Trainer.java create mode 100644 domain/src/main/java/akkamon/domain/actors/AkkamonBattle.java create mode 100644 domain/src/main/java/akkamon/domain/actors/AkkamonNexus.java create mode 100644 domain/src/main/java/akkamon/domain/actors/SceneTrainerGroup.java create mode 100644 domain/src/main/java/akkamon/domain/actors/Trainer.java diff --git a/api/src/main/java/akkamon/api/EventSocket.java b/api/src/main/java/akkamon/api/EventSocket.java index 77950ed..0f92878 100644 --- a/api/src/main/java/akkamon/api/EventSocket.java +++ b/api/src/main/java/akkamon/api/EventSocket.java @@ -1,5 +1,6 @@ package akkamon.api; +import akkamon.domain.actors.AkkamonNexus; import akkamon.domain.AkkamonSession; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketAdapter; @@ -10,7 +11,7 @@ import java.util.concurrent.CountDownLatch; public class EventSocket extends WebSocketAdapter implements AkkamonSession { private final CountDownLatch closureLatch = new CountDownLatch(1); - private String trainerId; + private AkkamonNexus.TrainerID trainerID; @Override public void onWebSocketConnect(Session sess) @@ -59,12 +60,12 @@ public class EventSocket extends WebSocketAdapter implements AkkamonSession { } @Override - public void setTrainerId(String trainerId) { - this.trainerId = trainerId; + public void settrainerID(AkkamonNexus.TrainerID trainerID) { + this.trainerID = trainerID; } @Override - public String getTrainerId() { - return trainerId; + public AkkamonNexus.TrainerID gettrainerID() { + return this.trainerID; } } diff --git a/api/src/main/java/akkamon/api/MessagingEngine.java b/api/src/main/java/akkamon/api/MessagingEngine.java index 957ad6e..fb913ca 100644 --- a/api/src/main/java/akkamon/api/MessagingEngine.java +++ b/api/src/main/java/akkamon/api/MessagingEngine.java @@ -4,7 +4,7 @@ import akka.actor.typed.ActorRef; import akka.actor.typed.ActorSystem; import akkamon.api.models.*; import akkamon.domain.AkkamonMessageEngine; -import akkamon.domain.AkkamonNexus; +import akkamon.domain.actors.AkkamonNexus; import akkamon.domain.AkkamonSession; import akkamon.domain.InteractionHandshaker; import com.google.gson.Gson; @@ -15,15 +15,17 @@ import java.util.concurrent.TimeUnit; public class MessagingEngine implements AkkamonMessageEngine { + // nexus: a connection or series of connections linking two or more things. + private ActorRef nexus; + private Map> sceneIdToAkkamonSessions = new HashMap<>(); - private Map trainerIdToAkkamonSessions = new HashMap<>(); + private Map trainerIDToAkkamonSessions = new HashMap<>(); private Map> pendingInteractioRequestToHandshaker = new HashMap<>(); - private Gson gson = new Gson(); - private ActorRef system; + private Gson gson = new Gson(); public MessagingEngine() { - this.system = ActorSystem.create(AkkamonNexus.create(this), "akkamon-system"); + this.nexus = ActorSystem.create(AkkamonNexus.create(this), "akkamon-system"); ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2); executor.scheduleAtFixedRate(new Runnable() { @@ -36,27 +38,29 @@ public class MessagingEngine implements AkkamonMessageEngine { } private void heartBeat() { - system.tell(new AkkamonNexus.RequestHeartBeat( + nexus.tell(new AkkamonNexus.RequestHeartBeat( UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE, - system + nexus )); } @Override - public void broadCastHeartBeatToScene(String sceneId, - Map trainerPositions) { + public void broadCastHeartBeatToScene(String sceneId, Map trainerPositions) { + Set sceneSessions = sceneIdToAkkamonSessions.get(sceneId); // System.out.println(sceneSessions); // System.out.println(sceneIdToAkkamonSessions.keySet()); if (sceneSessions != null) { for (AkkamonSession session : sceneSessions) { - Map withoutSelf = new HashMap<>(trainerPositions); - withoutSelf.remove(session.getTrainerId()); + Map withoutSelf = new HashMap<>(trainerPositions); + withoutSelf.remove(session.gettrainerID()); + HeartBeatEvent heartBeat = new HeartBeatEvent( withoutSelf ); + String heartBeatMessage = gson.toJson(heartBeat); - // System.out.println("Sending to " + session.getTrainerId()); + // System.out.println("Sending to " + session.gettrainerID()); // System.out.println(heartBeatMessage); session.send( heartBeatMessage @@ -66,14 +70,21 @@ public class MessagingEngine implements AkkamonMessageEngine { } @Override - public void broadCastInteractionRequestToSessionWithTrainerIds(List trainerIds, String type, String trainerId, String requestName, ActorRef handshaker) { + public void broadCastInteractionRequestToSessionWithtrainerIDs( + List trainerIDs, + String type, + AkkamonNexus.TrainerID trainerID, + String requestName, + ActorRef handshaker) { + System.out.println("Sending interaction request " + requestName); this.pendingInteractioRequestToHandshaker.put(requestName, handshaker); - trainerIds.add(trainerId); - for (String id : trainerIds) { - AkkamonSession session = trainerIdToAkkamonSessions.get(id); + trainerIDs.add(trainerID); + + for (AkkamonNexus.TrainerID id : trainerIDs) { + AkkamonSession session = trainerIDToAkkamonSessions.get(id); if (session != null) { - session.send(gson.toJson(new InteractionRequest( + session.send(gson.toJson(new OutgoingInteractionRequest( type, id, requestName @@ -86,33 +97,35 @@ public class MessagingEngine implements AkkamonMessageEngine { @Override - public void registerTrainerSessionToSceneAndTrainerIdMaps(String sceneId, AkkamonSession session) { - System.out.println("Registering session to scene " + sceneId); - Set sceneIdMapping = sceneIdToAkkamonSessions.get(sceneId); - AkkamonSession trainerIdMapping = trainerIdToAkkamonSessions.get(session.getTrainerId()); + public void registerTrainerSessionToSceneAndtrainerIDMaps(AkkamonNexus.TrainerID trainerID, AkkamonSession session) { + System.out.println("Registering session to scene " + trainerID.scene); + + Set sceneIdMapping = sceneIdToAkkamonSessions.get(trainerID.scene); + // AkkamonSession trainerIDMapping = trainerIDToAkkamonSessions.get(session.gettrainerID()); + if (sceneIdMapping != null) { sceneIdMapping.add(session); } else { sceneIdMapping = new HashSet<>(); sceneIdMapping.add(session); - sceneIdToAkkamonSessions.put(sceneId, + sceneIdToAkkamonSessions.put(trainerID.scene, sceneIdMapping ); System.out.println(sceneIdToAkkamonSessions.keySet()); } - trainerIdToAkkamonSessions.put(session.getTrainerId(), session); - System.out.println(trainerIdToAkkamonSessions); + trainerIDToAkkamonSessions.put(session.gettrainerID(), session); + System.out.println(trainerIDToAkkamonSessions); - System.out.println("Sending trainerId: " + session.getTrainerId()); + System.out.println("Sending trainerID: " + session.gettrainerID()); // TODO what if registration goes wrong ... session.send( - gson.toJson(new TrainerRegistrationReplyEvent(session.getTrainerId())) + gson.toJson(new TrainerRegistrationReplyEvent(session.gettrainerID())) ); } @Override - public void removeTrainerSessionFromScene(String sceneId, AkkamonSession session) { + public void removeTrainerSessionFromScene(AkkamonNexus.TrainerID sceneId, AkkamonSession session) { this.sceneIdToAkkamonSessions.get(sceneId).remove(session); } @@ -123,12 +136,12 @@ public class MessagingEngine implements AkkamonMessageEngine { if (entry.getValue().contains(session)) sceneId = entry.getKey(); } - system.tell(new AkkamonNexus.RequestTrainerOffline( + nexus.tell(new AkkamonNexus.RequestTrainerOffline( UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE, - session.getTrainerId(), + session.gettrainerID(), sceneId, session, - system + nexus )); } @@ -138,9 +151,10 @@ public class MessagingEngine implements AkkamonMessageEngine { } @Override - public void broadCastInteractionStart(String requestName, String interactionType, Set waitingToStartInteraction) { - for (String trainerId : waitingToStartInteraction) { - AkkamonSession session = trainerIdToAkkamonSessions.get(trainerId); + public void broadCastInteractionStart(String requestName, String interactionType, Set waitingToStartInteraction) { + for (AkkamonNexus.TrainerID trainerID : waitingToStartInteraction) { + + AkkamonSession session = trainerIDToAkkamonSessions.get(trainerID); session.send(gson.toJson( new InteractionStartEvent( requestName, @@ -152,75 +166,74 @@ public class MessagingEngine implements AkkamonMessageEngine { } @Override - public void broadCastHandshakeFail(String requestName, Set waitingToStartInteraction) { + public void broadCastHandshakeFail(String requestName, Set waitingToStartInteraction) { System.out.println("Handshake fail not implemented yet!"); } void incoming(AkkamonSession session, String message) { + System.out.println(message); Event event = gson.fromJson(message, Event.class); if (event == null) { System.out.println("Received non-supported message DTO."); return; } - // TODO use session trainerId + // TODO use session trainerID String sceneId = "DemoScene"; switch (event.type) { case INTERACTION_REPLY: System.out.println("received interaction reply!"); - sendToHandshaker(event.requestName, event.trainerId, event.sceneId, event.value); + sendToHandshaker(event.requestName, event.trainerID, event.value); break; case INTERACTION_REQUEST: System.out.println("received interaction request"); System.out.println(event.interaction); - system.tell(new AkkamonNexus.RequestInteraction( + nexus.tell(new AkkamonNexus.RequestInteraction( UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE, event.interaction.type, - event.sceneId, - event.interaction.requestingTrainerId, - event.interaction.receivingTrainerIds, - system + event.trainerID, + event.interaction.receivingtrainerIDs, + nexus )); break; case START_MOVING: - system.tell(new AkkamonNexus.RequestStartMoving( + System.out.println(message); + nexus.tell(new AkkamonNexus.RequestStartMoving( UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE, - session.getTrainerId(), - event.sceneId, + event.trainerID, event.direction, - system + nexus )); break; case NEW_TILE_POS: - system.tell( + nexus.tell( new AkkamonNexus.RequestNewTilePos( UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE, - session.getTrainerId(), - event.sceneId, + event.trainerID, event.tilePos, - system + nexus ) ); break; case STOP_MOVING: - system.tell( + nexus.tell( new AkkamonNexus.RequestStopMoving( UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE, - session.getTrainerId(), - event.sceneId, + event.trainerID, event.direction, - system + nexus ) ); break; case TRAINER_REGISTRATION_REQUEST: - String trainerId = String.valueOf(sceneIdToAkkamonSessions.get(sceneId) == null ? 1 : sceneIdToAkkamonSessions.get(sceneId).size() + 1); - system.tell(new AkkamonNexus.RequestTrainerRegistration( - trainerId, + // Here we make the trainerID and the scene is hard coded! + String trainerName = String.valueOf(sceneIdToAkkamonSessions.get(sceneId) == null ? 1 : sceneIdToAkkamonSessions.get(sceneId).size() + 1); + nexus.tell(new AkkamonNexus.RequestTrainerRegistration( + trainerName, sceneId, session, - system + nexus )); break; case HEART_BEAT: @@ -230,11 +243,11 @@ public class MessagingEngine implements AkkamonMessageEngine { } - private void sendToHandshaker(String requestName, String trainerId, String sceneId, boolean value) { + private void sendToHandshaker(String requestName, AkkamonNexus.TrainerID trainerID, boolean value) { ActorRef handshaker = pendingInteractioRequestToHandshaker.get(requestName); if (handshaker != null) { handshaker.tell( - new InteractionHandshaker.InteractionReply(requestName, trainerId, sceneId, value) + new InteractionHandshaker.InteractionReply(requestName, trainerID, value) ); } } diff --git a/api/src/main/java/akkamon/api/models/Event.java b/api/src/main/java/akkamon/api/models/Event.java index 4743453..06ca8d0 100644 --- a/api/src/main/java/akkamon/api/models/Event.java +++ b/api/src/main/java/akkamon/api/models/Event.java @@ -1,13 +1,13 @@ package akkamon.api.models; +import akkamon.domain.actors.AkkamonNexus; import akkamon.domain.Direction; import akkamon.domain.TilePos; public class Event { public EventType type; - public String trainerId; + public AkkamonNexus.TrainerID trainerID; public Direction direction; - public String sceneId; public TilePos tilePos; public Interaction interaction; public String requestName; diff --git a/api/src/main/java/akkamon/api/models/HeartBeatEvent.java b/api/src/main/java/akkamon/api/models/HeartBeatEvent.java index b795feb..2ec98ad 100644 --- a/api/src/main/java/akkamon/api/models/HeartBeatEvent.java +++ b/api/src/main/java/akkamon/api/models/HeartBeatEvent.java @@ -1,13 +1,13 @@ package akkamon.api.models; -import akkamon.domain.AkkamonNexus; +import akkamon.domain.actors.AkkamonNexus; import java.util.Map; public class HeartBeatEvent extends Event { - public Map remoteMovementQueues; + public Map remoteMovementQueues; - public HeartBeatEvent(Map remoteMovementQueues) { + public HeartBeatEvent(Map remoteMovementQueues) { this.type = EventType.HEART_BEAT; this.remoteMovementQueues = remoteMovementQueues; } diff --git a/api/src/main/java/akkamon/api/models/Interaction.java b/api/src/main/java/akkamon/api/models/Interaction.java index 5bc9d17..ad947e9 100644 --- a/api/src/main/java/akkamon/api/models/Interaction.java +++ b/api/src/main/java/akkamon/api/models/Interaction.java @@ -1,21 +1,22 @@ package akkamon.api.models; +import akkamon.domain.actors.AkkamonNexus; + import java.util.List; public class Interaction { public String type; - public String requestingTrainerId; - public List receivingTrainerIds; + public String requestingtrainerID; + public List receivingtrainerIDs; - public Interaction(String type, String requestingTrainerId, List receivingTrainerIds) { + public Interaction(String type, String requestingtrainerID, List receivingtrainerIDs) { this.type = type; - this.requestingTrainerId = requestingTrainerId; - this.receivingTrainerIds = receivingTrainerIds; + this.receivingtrainerIDs = receivingtrainerIDs; } public String toString() { return "interaction={\n\ttype: " + this.type + ",\n" + - "\trequestingTrainerId: " + this.requestingTrainerId + ",\n" + - "\treceivingTrainerIds: " + this.receivingTrainerIds + "\n}"; + "\trequestingtrainerID: " + this.requestingtrainerID + ",\n" + + "\treceivingtrainerIDs: " + this.receivingtrainerIDs + "\n}"; } } diff --git a/api/src/main/java/akkamon/api/models/InteractionRequest.java b/api/src/main/java/akkamon/api/models/InteractionRequest.java deleted file mode 100644 index ae91972..0000000 --- a/api/src/main/java/akkamon/api/models/InteractionRequest.java +++ /dev/null @@ -1,13 +0,0 @@ -package akkamon.api.models; - -public class InteractionRequest extends Event { - - public String interactionType; - - public InteractionRequest(String interactionType, String trainerId, String requestName) { - this.type = EventType.INTERACTION_REQUEST; - this.interactionType = interactionType; - this.trainerId = trainerId; - this.requestName = requestName; - } -} diff --git a/api/src/main/java/akkamon/api/models/OutgoingInteractionRequest.java b/api/src/main/java/akkamon/api/models/OutgoingInteractionRequest.java new file mode 100644 index 0000000..9360384 --- /dev/null +++ b/api/src/main/java/akkamon/api/models/OutgoingInteractionRequest.java @@ -0,0 +1,15 @@ +package akkamon.api.models; + +import akkamon.domain.actors.AkkamonNexus; + +public class OutgoingInteractionRequest extends Event { + + public String interactionType; + + public OutgoingInteractionRequest(String interactionType, AkkamonNexus.TrainerID trainerID, String requestName) { + this.type = EventType.INTERACTION_REQUEST; + this.interactionType = interactionType; + this.trainerID = trainerID; + this.requestName = requestName; + } +} diff --git a/api/src/main/java/akkamon/api/models/TrainerRegistrationReplyEvent.java b/api/src/main/java/akkamon/api/models/TrainerRegistrationReplyEvent.java index 107b722..2120e45 100644 --- a/api/src/main/java/akkamon/api/models/TrainerRegistrationReplyEvent.java +++ b/api/src/main/java/akkamon/api/models/TrainerRegistrationReplyEvent.java @@ -1,13 +1,12 @@ package akkamon.api.models; -import akkamon.domain.AkkamonNexus; - -import java.util.Map; +import akkamon.domain.actors.AkkamonNexus; public class TrainerRegistrationReplyEvent extends Event { - public TrainerRegistrationReplyEvent(String sessionTrainerId) { + + public TrainerRegistrationReplyEvent(AkkamonNexus.TrainerID sessiontrainerID) { this.type = EventType.TRAINER_REGISTRATION_REPLY; - this.trainerId = sessionTrainerId; + this.trainerID = sessiontrainerID; } } diff --git a/client/src/akkamon/client/Client.ts b/client/src/akkamon/client/Client.ts index 0e7e8c3..4adaec5 100644 --- a/client/src/akkamon/client/Client.ts +++ b/client/src/akkamon/client/Client.ts @@ -39,7 +39,10 @@ import { HeartBeatReplyEvent, Interaction, OutgoingInteractionRequestEvent, - InteractionReplyEvent + InteractionReplyEvent, + StartMovingEvent, + NewTilePosEvent, + StopMovingEvent } from './OutgoingEvents'; @@ -85,9 +88,10 @@ export class Client implements AkkamonClient this.send(new HeartBeatReplyEvent()); break; case EventType.TRAINER_REGISTRATION_REPLY: - if (event.trainerId !== undefined) { - console.log("setting Session trainerId to: " + event.trainerId); - this.session.trainerId = event.trainerId; + if (event.trainerID !== undefined) { + console.log("setting Session trainerID to: "); + console.log(event.trainerID); + this.session.trainerID = event.trainerID; } break; case EventType.INTERACTION_REQUEST: @@ -213,23 +217,19 @@ export class Client implements AkkamonClient sendInteractionRequest(interaction: Interaction) { console.log("sent an interaction request!"); - console.log(this.getCurrentSceneKey()); + console.log(this.getTrainerID()); console.log(JSON.stringify(interaction)); this.interactionEngine!.setAwaitingInteractionRequestInitiation(true); this.send(new OutgoingInteractionRequestEvent( - this.getCurrentSceneKey(), + this.getTrainerID()!, interaction )); } - getSessionTrainerId() { - return this.session.trainerId; - } - - getCurrentSceneKey() { - return this.scene!.scene.key; + getTrainerID() { + return this.session.trainerID; } sendInteractionReply(value: boolean, requestName: string) { @@ -237,10 +237,30 @@ export class Client implements AkkamonClient this.interactionEngine!.setAnswering(false); this.interactionEngine!.setWaitingForInteractionToStart(true); this.send(new InteractionReplyEvent( - this.getSessionTrainerId()!, - this.getCurrentSceneKey()!, + this.getTrainerID()!, requestName, value )); } + + sendStartMove(direction: Direction) { + this.send(new StartMovingEvent(this.getTrainerID()!, direction)); + } + + sendNewTilePos(tilePos: {x: number, y: number}) { + this.send(new NewTilePosEvent( + this.getTrainerID()!, tilePos + ) + ); + } + + sendStopMoving(direction: Direction) { + this.send( + new StopMovingEvent( + this.getTrainerID()!, + direction + ) + ); + + } } diff --git a/client/src/akkamon/client/IncomingEvents.ts b/client/src/akkamon/client/IncomingEvents.ts index 1145b4c..be02bf9 100644 --- a/client/src/akkamon/client/IncomingEvents.ts +++ b/client/src/akkamon/client/IncomingEvents.ts @@ -2,10 +2,11 @@ import { EventType, AkkamonEvent } from './EventType'; import type { Direction } from '../render/Direction'; +type TrainerID = {id: string, scene: string} export interface IncomingEvent extends AkkamonEvent { - remoteMovementQueues?:{[trainerId: string]: { value: Array }} - trainerId?: string + remoteMovementQueues?:{[trainerID: string]: { value: Array }} + trainerID?: TrainerID requestId?: number requestName?: string interactionType?: string @@ -17,7 +18,7 @@ export class IncomingInteractionRequest implements IncomingEvent { constructor( public interactionType: string, - public trainerId: string, + public trainerID: TrainerID, public requestName: string ) { } } @@ -35,7 +36,7 @@ export class PlayerRegistrationReplyEvent implements IncomingEvent { public type: EventType = EventType.TRAINER_REGISTRATION_REPLY; constructor( - public trainerId: string + public trainerID: TrainerID ) { } } diff --git a/client/src/akkamon/client/InteractionEngine.ts b/client/src/akkamon/client/InteractionEngine.ts index f54ced4..ee5b4ac 100644 --- a/client/src/akkamon/client/InteractionEngine.ts +++ b/client/src/akkamon/client/InteractionEngine.ts @@ -47,7 +47,7 @@ export class InteractionEngine extends AkkamonEngine { this.answering = true; this.scene.pushMenu(new InteractionRequestDialogue(this.scene, ["YES", "NO"], - {name: message!.trainerId, + {name: message!.trainerID.id, requestType: message!.interactionType, requestName: message!.requestName} )); @@ -59,7 +59,7 @@ export class InteractionEngine extends AkkamonEngine { } push(event: IncomingInteractionRequest) { - // check trainerId + // check trainerID if (this.awaitingInit) { this.waitingForResponseOf = event.requestName; diff --git a/client/src/akkamon/client/OutgoingEvents.ts b/client/src/akkamon/client/OutgoingEvents.ts index 9feb49b..6c19b07 100644 --- a/client/src/akkamon/client/OutgoingEvents.ts +++ b/client/src/akkamon/client/OutgoingEvents.ts @@ -2,20 +2,33 @@ import type { Direction } from '../render/Direction'; import { EventType, AkkamonEvent } from './EventType'; +export type TrainerID = { + id: string, + scene: string +} + +interface OutgoingEvent extends AkkamonEvent { + trainerID: TrainerID; +} + export type RemoteMovementQueues = { - [trainerId: string]: { value: Array } + [trainerID: string]: { value: Array } } export type Interaction = { type: string, - requestingTrainerId: string, - receivingTrainerIds: string[] + receivingtrainerIDs: TrainerID[] } -export interface InteractionEvent extends AkkamonEvent { +export interface InteractionEvent extends OutgoingEvent { interaction: Interaction } + + + + + export class HeartBeatReplyEvent implements AkkamonEvent { public type: EventType = EventType.HEART_BEAT; @@ -34,54 +47,53 @@ export class PlayerRegistrationRequestEvent implements AkkamonEvent { } -export class StartMovingEvent implements AkkamonEvent { +export class StartMovingEvent implements OutgoingEvent { public type: EventType = EventType.START_MOVING; constructor( - public sceneId: string, + public trainerID: TrainerID, public direction: Direction, ) { } } -export class StopMovingEvent implements AkkamonEvent { +export class StopMovingEvent implements OutgoingEvent { public type: EventType = EventType.STOP_MOVING; constructor( - public sceneId: string, + public trainerID: TrainerID, public direction: Direction, ) { } } -export class NewTilePosEvent implements AkkamonEvent { +export class NewTilePosEvent implements OutgoingEvent { public type: EventType = EventType.NEW_TILE_POS; constructor( - public sceneId: string, + public trainerID: TrainerID, public tilePos: {x: number, y: number} ) { } } -export class OutgoingInteractionRequestEvent implements InteractionEvent { +export class OutgoingInteractionRequestEvent implements OutgoingEvent { public type: EventType = EventType.INTERACTION_REQUEST; constructor( - public sceneId: string, + public trainerID: TrainerID, public interaction: Interaction, ) { } } -export class InteractionReplyEvent implements AkkamonEvent { +export class InteractionReplyEvent implements OutgoingEvent { public type: EventType = EventType.INTERACTION_REPLY; constructor( - public trainerId: string, - public sceneId: string, + public trainerID: TrainerID, public requestName: string, public value: boolean ) { } diff --git a/client/src/akkamon/client/Session.ts b/client/src/akkamon/client/Session.ts index 6b6f5ec..7357743 100644 --- a/client/src/akkamon/client/Session.ts +++ b/client/src/akkamon/client/Session.ts @@ -1,11 +1,7 @@ -import type Player from './player'; +import type { + TrainerID +} from './OutgoingEvents'; export default interface AkkamonSession extends WebSocket { - trainerId?: string - -} - -interface User { - name: string - password: string + trainerID?: TrainerID; } diff --git a/client/src/akkamon/client/Socket.ts b/client/src/akkamon/client/Socket.ts index b46c73f..3184416 100644 --- a/client/src/akkamon/client/Socket.ts +++ b/client/src/akkamon/client/Socket.ts @@ -7,7 +7,7 @@ import { export class Socket extends WebSocket implements AkkamonSession { - public trainerId?: string; + public trainerID?: {id: string, scene: string}; constructor( url: string, diff --git a/client/src/akkamon/render/engine/GridPhysics.ts b/client/src/akkamon/render/engine/GridPhysics.ts index 7c266bf..4a7d1e1 100644 --- a/client/src/akkamon/render/engine/GridPhysics.ts +++ b/client/src/akkamon/render/engine/GridPhysics.ts @@ -66,9 +66,7 @@ export class GridPhysics extends AkkamonEngine { private startMoving(direction: Direction): void { console.log("Sending startMovingEvent"); - this.client.send( - new StartMovingEvent(this.playerSprite.getScene(), direction) - ); + this.client.sendStartMove(direction); this.playerSprite.startAnimation(direction); this.movementDirection = direction; this.updatePlayerSpriteTilePosition(); @@ -96,12 +94,7 @@ export class GridPhysics extends AkkamonEngine { } private updatePlayerSpriteTilePosition() { - this.client.send( - new NewTilePosEvent( - this.playerSprite.getScene(), - this.playerSprite.getTilePos() - ) - ); + this.client.sendNewTilePos(this.playerSprite.getTilePos()); this.playerSprite.setTilePos( this.playerSprite .getTilePos() @@ -149,12 +142,7 @@ export class GridPhysics extends AkkamonEngine { } private stopMoving(): void { - this.client.send( - new StopMovingEvent( - this.playerSprite.getScene(), - this.movementDirection - ) - ); + this.client.sendStopMoving(this.movementDirection); this.playerSprite.stopAnimation(this.movementDirection); this.movementDirection = Direction.NONE; } diff --git a/client/src/akkamon/render/engine/RemotePlayerEngine.ts b/client/src/akkamon/render/engine/RemotePlayerEngine.ts index 7ce5fa9..037b26c 100644 --- a/client/src/akkamon/render/engine/RemotePlayerEngine.ts +++ b/client/src/akkamon/render/engine/RemotePlayerEngine.ts @@ -24,7 +24,7 @@ export class RemotePlayerEngine extends AkkamonEngine { private scene: WorldScene; - private trainerIdToRemotePlayerSprite: Map = new Map(); + private trainerIDToRemotePlayerSprite: Map = new Map(); constructor(scene: WorldScene) { super(); @@ -37,13 +37,13 @@ export class RemotePlayerEngine extends AkkamonEngine { } pushMovesToSprites(remoteMovementQueues: RemoteMovementQueues) { - this.trainerIdToRemotePlayerSprite.forEach((remoteSprite: RemotePlayerSprite, key: string) => { + this.trainerIDToRemotePlayerSprite.forEach((remoteSprite: RemotePlayerSprite, key: string) => { remoteSprite.push(remoteMovementQueues[key].value); }); } update(delta: number): void { - this.trainerIdToRemotePlayerSprite.forEach((remoteSprite: RemotePlayerSprite, key: string) => { + this.trainerIDToRemotePlayerSprite.forEach((remoteSprite: RemotePlayerSprite, key: string) => { if (remoteSprite.isMoving()) { console.log("remote player currently walking"); remoteSprite.updatePixelPosition(delta); @@ -55,18 +55,18 @@ export class RemotePlayerEngine extends AkkamonEngine { } updateMembers(newRemoteMovementQueues: RemoteMovementQueues) { - const traineridToQueueValue = newRemoteMovementQueues; + const trainerIDToQueueValue = newRemoteMovementQueues; Object.keys(newRemoteMovementQueues).forEach((key: string) => { - var moveQueue = traineridToQueueValue[key].value; + var moveQueue = trainerIDToQueueValue[key].value; if (moveQueue !== undefined) { // console.log("-> key: " + key + " has position " + newTilePos.x + ", " + newTilePos.y); - if (!this.trainerIdToRemotePlayerSprite.has(key)) { + if (!this.trainerIDToRemotePlayerSprite.has(key)) { // console.log("adding remote player sprite for " + key); - this.trainerIdToRemotePlayerSprite.set(key, + this.trainerIDToRemotePlayerSprite.set(key, new RemotePlayerSprite({ scene: this.scene, tilePos: new Phaser.Math.Vector2(this.scene.spawnPointTilePos!), @@ -82,11 +82,11 @@ export class RemotePlayerEngine extends AkkamonEngine { }); - this.trainerIdToRemotePlayerSprite.forEach((value: RemotePlayerSprite, key: string) => { + this.trainerIDToRemotePlayerSprite.forEach((value: RemotePlayerSprite, key: string) => { if (!(key in newRemoteMovementQueues)) { // console.log("removing remote player sprite for " + key); - this.trainerIdToRemotePlayerSprite.get(key)!.destroy(); - this.trainerIdToRemotePlayerSprite.delete(key); + this.trainerIDToRemotePlayerSprite.get(key)!.destroy(); + this.trainerIDToRemotePlayerSprite.delete(key); } else { // console.log("Player " + key + " was not removed!"); } @@ -94,6 +94,6 @@ export class RemotePlayerEngine extends AkkamonEngine { } getData() { - return this.trainerIdToRemotePlayerSprite; + return this.trainerIDToRemotePlayerSprite; } } diff --git a/client/src/akkamon/scenes/AkkamonWorldScene.ts b/client/src/akkamon/scenes/AkkamonWorldScene.ts index 002b4b5..9912145 100644 --- a/client/src/akkamon/scenes/AkkamonWorldScene.ts +++ b/client/src/akkamon/scenes/AkkamonWorldScene.ts @@ -147,8 +147,8 @@ export function akkamonBaseWorldScene(key: string): WorldScene { requestBattle: function (remotePlayerName: string | string[]): void { this.client.sendInteractionRequest({ type: "battle", - requestingTrainerId: this.client.getSessionTrainerId()!, - receivingTrainerIds: Array.isArray(remotePlayerName) ? remotePlayerName : [remotePlayerName] + requestingtrainerID: this.client.getSessiontrainerID()!, + receivingtrainerIDs: Array.isArray(remotePlayerName) ? remotePlayerName : [remotePlayerName] }); }, @@ -265,8 +265,8 @@ export function akkamonBaseWorldScene(key: string): WorldScene { // requestBattle(remotePlayerName: string | string[]): void { // this.client.sendInteractionRequest({ // type: "battle", -// requestingTrainerId: this.client.getSessionTrainerId()!, -// receivingTrainerIds: Array.isArray(remotePlayerName) ? remotePlayerName : [remotePlayerName] +// requestingtrainerID: this.client.getSessiontrainerID()!, +// receivingtrainerIDs: Array.isArray(remotePlayerName) ? remotePlayerName : [remotePlayerName] // }); // } // diff --git a/client/src/akkamon/scenes/UIElement.ts b/client/src/akkamon/scenes/UIElement.ts index c77503e..1c87ef4 100644 --- a/client/src/akkamon/scenes/UIElement.ts +++ b/client/src/akkamon/scenes/UIElement.ts @@ -163,12 +163,6 @@ class Menu extends Phaser.GameObjects.Image implements AkkamonMenu { return index * this.ySpacing! + this.yOffsetFromTop! + 7 + this.y; } - setButtons(buttonTextArray: Array) { - for (let i = 0; i < buttonTextArray.length; i++) { - this.buttons!.push(new MenuText(this.scene, this.group!, this.groupDepth!, this.x - this.xOffsetFromRight!, this.y + this.yOffsetFromTop! + i * this.ySpacing!, buttonTextArray[i])); - } - } - clearButtons() { for (let button of this.buttons!) { button.destroy(); @@ -202,6 +196,13 @@ export class PauseMenu extends Menu implements AkkamonMenu { this.group!.setDepth(this.groupDepth!); } + setButtons(buttonTextArray: Array) { + for (let i = 0; i < buttonTextArray.length; i++) { + this.buttons!.push(new MenuText(this.scene, this.group!, this.groupDepth!, this.x - this.xOffsetFromRight!, this.y + this.yOffsetFromTop! + i * this.ySpacing!, buttonTextArray[i])); + } + } + + confirm() { if (this.buttons![this.index!].text === 'PHONE') { this.akkamonScene.pushMenu(new RemotePlayerList(this.akkamonScene, this.akkamonScene.getRemotePlayerNames())); @@ -210,7 +211,7 @@ export class PauseMenu extends Menu implements AkkamonMenu { } class ListMenu extends Menu implements AkkamonMenu { - options: Array + options: Array viewTop: number = 0; @@ -218,7 +219,7 @@ class ListMenu extends Menu implements AkkamonMenu { constructor( scene: WorldScene, - options: Array + options: any[] ) { super(scene, "pause-menu") let camera = scene.cameras.main; @@ -232,28 +233,14 @@ class ListMenu extends Menu implements AkkamonMenu { this.xOffsetFromRight = 210; this.yOffsetFromTop = 50; - let contacts = new MenuText( - this.scene, - this.group!, - this.groupDepth!, - this.x - this.xOffsetFromRight, - this.y + 20, - "Nearby trainers:") - // this.yOffsetFromTop - // this.ySpacing - this.setPicker(0); - this.setButtons( - this.options.slice( - this.viewTop, - this.viewBot - ) - ); this.groupDepth = 30; this.group!.setDepth(this.groupDepth); } + setButtons(visibleOptions: any[]) {} + selectButton(direction: Direction) { if (direction === Direction.UP) { if (this.index! !== 0) { @@ -292,12 +279,51 @@ class ListMenu extends Menu implements AkkamonMenu { class RemotePlayerList extends ListMenu implements AkkamonMenu { + constructor( + scene: WorldScene, + options: {id: string, scene: string}[] + ) { + super(scene, options) + + let title = new MenuText( + this.scene, + this.group!, + this.groupDepth!, + this.x - this.xOffsetFromRight!, + this.y + 20, + "Nearby trainers:") + + this.setButtons( + this.options.slice( + this.viewTop, + this.viewBot + ) + ); + + // this.yOffsetFromTop + // this.ySpacing + + } + + setButtons(visibleOptions: {id: string, scene: string}[]) { + for (let i = 0; i < visibleOptions.length; i++) { + + this.buttons!.push(new MenuText( + this.scene, + this.group!, + this.groupDepth!, + this.x - this.xOffsetFromRight!, + this.y + this.yOffsetFromTop! + i * this.ySpacing!, + visibleOptions[i].id)); + } + } + confirm() { this.akkamonScene.pushMenu(new ChallengeDialogue( this.akkamonScene, ['YES', 'NO'], { - 'trainerName': this.buttons![this.index! + this.viewTop].text + 'trainerID': this.options![this.index! + this.viewTop] })); } } @@ -307,7 +333,7 @@ class ConfirmationDialogue extends Menu implements AkkamonMenu { options?: Array dialogueBox?: Dialogue - constructor(scene: WorldScene, options: Array, dialogueData: {[key: string]: string} | string) { + constructor(scene: WorldScene, options: Array, dialogueData: {[key: string]: string} | string | {}) { super(scene, "confirmation-dialogue"); let camera = scene.cameras.main; this.setDisplaySize(200, 0.83 * 200) @@ -323,6 +349,14 @@ class ConfirmationDialogue extends Menu implements AkkamonMenu { this.dialogueBox = new Dialogue(scene, this.group!, this.groupDepth); } + + setButtons(buttonTextArray: Array) { + for (let i = 0; i < buttonTextArray.length; i++) { + this.buttons!.push(new MenuText(this.scene, this.group!, this.groupDepth!, this.x - this.xOffsetFromRight!, this.y + this.yOffsetFromTop! + i * this.ySpacing!, buttonTextArray[i])); + } + } + + } class Dialogue extends Phaser.GameObjects.Image implements AkkamonMenu { @@ -416,19 +450,21 @@ class Dialogue extends Phaser.GameObjects.Image implements AkkamonMenu { } class ChallengeDialogue extends ConfirmationDialogue implements AkkamonMenu { - challengedTrainerName: string; - constructor(scene: WorldScene, options: Array, dialogueData: {[key: string]: string}) { + challengedTrainerID: {id: string, scene: string}; + + constructor(scene: WorldScene, options: Array, dialogueData: {trainerID: {id: string, scene: string}}) { + super(scene, options, dialogueData); - this.challengedTrainerName = dialogueData['trainerName']; + this.challengedTrainerID = dialogueData.trainerID; this.dialogueBox!.push( - `Do you want to challenge ${this.challengedTrainerName} to a battle?` + `Do you want to challenge ${this.challengedTrainerID.id} to a battle?` ); this.dialogueBox!.displayNextDialogue(); } confirm() { if (this.buttons![this.index!].text === "YES") { - this.akkamonScene.requestBattle(this.challengedTrainerName); + this.akkamonScene.requestBattle(this.challengedTrainerID); this.akkamonScene.clearMenus(); this.akkamonScene.pushMenu(new WaitingDialogue(this.akkamonScene, new Phaser.GameObjects.Group(this.scene), 20, 'Awaiting request initialisation...')); } else { diff --git a/client/src/akkamon/scenes/WorldScene.ts b/client/src/akkamon/scenes/WorldScene.ts index f125a0e..f570cde 100644 --- a/client/src/akkamon/scenes/WorldScene.ts +++ b/client/src/akkamon/scenes/WorldScene.ts @@ -40,9 +40,9 @@ export interface WorldScene extends Phaser.Scene { getPlayerPixelPosition: () => Phaser.Math.Vector2 - getRemotePlayerNames: () => string[] + getRemotePlayerNames: () => {id: string, scene: string}[] - requestBattle: (remotePlayerData: string | string[]) => void + requestBattle: (remotePlayerData: {id: string, scene: string}) => void clearMenus: () => void @@ -136,20 +136,26 @@ export function createWorldScene(scene: Pha return client.requestPlayerPixelPosition(); } - getRemotePlayerNames(): Array { + getRemotePlayerNames(): Array<{id: string, scene: string}> { let remotePlayerData = client.requestRemotePlayerData(); if (remotePlayerData.size === 0) { - return ['Nobody Online']; + return [{id: 'Nobody Online', scene: ''}]; } else { - return Array.from(remotePlayerData.keys()); + let keys = remotePlayerData.keys(); + let trainerIDs: {id: string, scene: string}[] = []; + + for (let key of keys) { + let trainerID: { id: string, scene: string } = JSON.parse(key); + trainerIDs.push(trainerID); + } + return trainerIDs; } } - requestBattle(remotePlayerName: string | string[]): void { + requestBattle(remoteTrainerID: {id: string, scene: string}): void { client.sendInteractionRequest({ type: "battle", - requestingTrainerId: client.getSessionTrainerId()!, - receivingTrainerIds: Array.isArray(remotePlayerName) ? remotePlayerName : [remotePlayerName] + receivingtrainerIDs: Array.isArray(remoteTrainerID) ? remoteTrainerID : [remoteTrainerID] }); } diff --git a/client/src/app.ts b/client/src/app.ts index 7a80442..82fdc81 100644 --- a/client/src/app.ts +++ b/client/src/app.ts @@ -39,8 +39,8 @@ function delay(ms: number) { async function awaitRegistrationReplyAndStart() { if (!game) { - while (client.getSessionTrainerId() === undefined) { - console.log("can't start game, this trainerId is still undefined"); + while (client.getTrainerID() === undefined) { + console.log("can't start game, this trainerID is still undefined"); await delay(1000); } newGame(); diff --git a/domain/src/main/java/akkamon/domain/AkkamonMessageEngine.java b/domain/src/main/java/akkamon/domain/AkkamonMessageEngine.java index df5402f..0172770 100644 --- a/domain/src/main/java/akkamon/domain/AkkamonMessageEngine.java +++ b/domain/src/main/java/akkamon/domain/AkkamonMessageEngine.java @@ -1,6 +1,7 @@ package akkamon.domain; import akka.actor.typed.ActorRef; +import akkamon.domain.actors.AkkamonNexus; import java.util.List; import java.util.Map; @@ -8,19 +9,19 @@ import java.util.Set; public interface AkkamonMessageEngine { // broadcasts position info to WebSocket Clients - void broadCastHeartBeatToScene(String sceneId, Map trainerPositions); + void broadCastHeartBeatToScene(String sceneId, Map trainerPositions); - void broadCastInteractionRequestToSessionWithTrainerIds(List trainerIds, String type, String trainerId, String requestName, ActorRef handshaker); + void broadCastInteractionRequestToSessionWithtrainerIDs(List trainerIDs, String type, AkkamonNexus.TrainerID trainerID, String requestName, ActorRef handshaker); - void registerTrainerSessionToSceneAndTrainerIdMaps(String sceneId, AkkamonSession session); + void registerTrainerSessionToSceneAndtrainerIDMaps(AkkamonNexus.TrainerID trainerID, AkkamonSession session); - void removeTrainerSessionFromScene(String sceneId, AkkamonSession session); + void removeTrainerSessionFromScene(AkkamonNexus.TrainerID trainerID, AkkamonSession session); void trainerDisconnected(AkkamonSession session); void removeInteractionHandshaker(String requestName); - void broadCastInteractionStart(String requestName, String interactionType, Set waitingToStartInteraction); + void broadCastInteractionStart(String requestName, String interactionType, Set waitingToStartInteraction); - void broadCastHandshakeFail(String requestName, Set waitingToStartInteraction); + void broadCastHandshakeFail(String requestName, Set waitingToStartInteraction); } diff --git a/domain/src/main/java/akkamon/domain/AkkamonNexus.java b/domain/src/main/java/akkamon/domain/AkkamonNexus.java deleted file mode 100644 index 949e5c5..0000000 --- a/domain/src/main/java/akkamon/domain/AkkamonNexus.java +++ /dev/null @@ -1,406 +0,0 @@ -package akkamon.domain; - -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.AbstractBehavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Receive; - -import java.time.Duration; -import java.util.*; - -public class AkkamonNexus extends AbstractBehavior { - - - public interface Command {} - - - public static class RespondInteractionHandshaker implements Command { - public String requestName; - public String interactionType; - public InteractionHandshaker.HandshakeResult result; - public Set waitingToStartInteraction; - - - public RespondInteractionHandshaker(String requestName, - String interactionType, - InteractionHandshaker.HandshakeResult result, - Set waitingToStartInteraction) { - this.requestName = requestName; - this.interactionType = interactionType; - this.result = result; - this.waitingToStartInteraction = waitingToStartInteraction; - } - } - - public static class RequestInteraction - implements Command { - - public long requestId; - public String type; - public String sceneId; - public String trainerId; - public List forwardTo; - public ActorRef replyTo; - - public RequestInteraction(long requestId, String type, String sceneId, String trainerId, List forwardTo, ActorRef replyTo) { - this.requestId = requestId; - this.type = type; - this.sceneId = sceneId; - this.trainerId = trainerId; - this.forwardTo = forwardTo; - this.replyTo = replyTo; - } - } - - public static class RequestTrainerRegistration - implements AkkamonNexus.Command, SceneTrainerGroup.Command { - public String trainerId; - public String sceneId; - public AkkamonSession session; - public ActorRef replyTo; - - public RequestTrainerRegistration( - String trainerId, - String sceneId, - AkkamonSession session, - ActorRef replyTo - ) { - this.trainerId = trainerId; - this.sceneId = sceneId; - this.session = session; - this.replyTo = replyTo; - } - } - - public static class TrainerRegistered implements Command { - private String trainerId; - private String sceneId; - private AkkamonSession session; - - public TrainerRegistered( - String trainerId, - String sceneId, - AkkamonSession session - ) { - this.trainerId = trainerId; - this.sceneId = sceneId; - this.session = session; - } - } - - public static class RequestStartMoving - implements Command, SceneTrainerGroup.Command, Trainer.Command { - public long requestId; - public String trainerId; - public String sceneId; - public Direction direction; - public ActorRef replyTo; - - public RequestStartMoving(long requestId, String trainerId, String sceneId, Direction direction, ActorRef replyTo) { - this.requestId = requestId; - this.trainerId = trainerId; - this.sceneId = sceneId; - this.direction = direction; - this.replyTo = replyTo; - } - } - - public static class RequestStopMoving - implements Command, SceneTrainerGroup.Command, Trainer.Command { - public long requestId; - public String trainerId; - public String sceneId; - public Direction direction; - public ActorRef replyTo; - - public RequestStopMoving( - long requestId, - String trainerId, - String sceneId, - Direction direction, - ActorRef replyTo) { - this.requestId = requestId; - this.trainerId = trainerId; - this.sceneId = sceneId; - this.direction = direction; - this.replyTo = replyTo; - } - } - - public static class RequestNewTilePos - implements Command, SceneTrainerGroup.Command, Trainer.Command { - public long requestId; - public String trainerId; - public String sceneId; - public TilePos tilePos; - public ActorRef replyTo; - - public RequestNewTilePos(long requestId, String trainerId, String sceneId, TilePos tilePos, ActorRef replyTo) { - this.requestId = requestId; - this.trainerId = trainerId; - this.sceneId = sceneId; - this.tilePos = tilePos; - this.replyTo = replyTo; - } - } - - public static class RequestHeartBeat - implements Command, SceneTrainerGroup.Command { - - public long requestId; - // TODO find a way to make the command Narrower - public ActorRef replyTo; - - public RequestHeartBeat(long requestId, ActorRef replyTo) { - this.requestId = requestId; - this.replyTo = replyTo; - } - } - - private static class SceneTrainerGroupTerminated implements AkkamonNexus.Command { - public SceneTrainerGroupTerminated(String sceneId) { - } - } - - public static class RequestTrainerOffline - implements Command, SceneTrainerGroup.Command, Trainer.Command { - public long requestId; - public String trainerId; - public String sceneId; - public AkkamonSession session; - public ActorRef replyTo; - - public RequestTrainerOffline(long requestId, String trainerId, String sceneId, AkkamonSession session, ActorRef replyTo) { - this.requestId = requestId; - this.trainerId = trainerId; - this.sceneId = sceneId; - this.session = session; - this.replyTo = replyTo; - } - } - - public static class RespondTrainerOffline - implements Command { - public long requestId; - public String sceneId; - public AkkamonSession session; - - public RespondTrainerOffline(long requestId, String sceneId, AkkamonSession session) { - this.requestId = requestId; - this.sceneId = sceneId; - this.session = session; - } - } - - public static class RespondHeartBeatQuery implements Command { - - public final long requestId; - public final String sceneId; - public final Map trainerMovementQueues; - - public RespondHeartBeatQuery( - long requestId, - String sceneId, - Map trainerPositions) { - this.requestId = requestId; - this.sceneId = sceneId; - this.trainerMovementQueues = trainerPositions; - } - } - - public interface MovementQueueReading { } - - public static class MovementQueue implements MovementQueueReading { - public final Queue value; - - public MovementQueue(Queue value) { - this.value = value; - } - } - - public enum MovementQueueEmpty implements MovementQueueReading { - INSTANCE - } - - public enum TrainerOffline implements MovementQueueReading { - INSTANCE - } - - public enum TrainerTimedOut implements MovementQueueReading { - INSTANCE - } - - public static Behavior create(AkkamonMessageEngine messagingEngine) { - return Behaviors.setup(context -> new AkkamonNexus(context, messagingEngine)); - } - - private AkkamonMessageEngine messageEngine; - private Map> sceneIdToActor = new HashMap<>(); - - public AkkamonNexus(ActorContext context, AkkamonMessageEngine msgEngine) { - super(context); - this.messageEngine = msgEngine; - getContext().getLog().info("AkkamonNexus is up and running, waiting eagerly for your messages!"); - } - - @Override - public Receive createReceive() { - return newReceiveBuilder() - .onMessage(RequestTrainerRegistration.class, this::onTrainerRegistration) - .onMessage(TrainerRegistered.class, this::onTrainerRegistered) - .onMessage(RequestTrainerOffline.class, this::onTrainerOfflineRequest) - .onMessage(RespondTrainerOffline.class, this::onTrainerOffline) - .onMessage(RequestHeartBeat.class, this::onHeartBeat) - .onMessage(RespondHeartBeatQuery.class, this::onHeartBeatQueryResponse) - .onMessage(RequestStartMoving.class, this::onStartMoving) - .onMessage(RequestStopMoving.class, this::onStopMoving) - .onMessage(RequestNewTilePos.class, this::onNewTilePos) - .onMessage(RequestInteraction.class, this::onInteractionRequest) - .onMessage(RespondInteractionHandshaker.class, this::onInteractionHandshakerResponse) - .build(); - } - - private Behavior onInteractionHandshakerResponse(RespondInteractionHandshaker r) { - this.messageEngine.removeInteractionHandshaker(r.requestName); - - if (r.result.equals(InteractionHandshaker.HandshakeResult.SUCCESS)) { - messageEngine.broadCastInteractionStart(r.requestName, r.interactionType, r.waitingToStartInteraction); - - } else if (r.result.equals(InteractionHandshaker.HandshakeResult.FAIL)) { - messageEngine.broadCastHandshakeFail(r.requestName, r.waitingToStartInteraction); - } - return this; - } - - private AkkamonNexus onInteractionRequest(RequestInteraction interactionRequest) { - List 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; - - ActorRef handshaker = getContext().spawn(InteractionHandshaker.create( - interactionRequest.trainerId, - interactionRequest.type, - interactionRequest.forwardTo, - interactionRequest.sceneId, - requestName, - interactionRequest.replyTo, - Duration.ofSeconds(60) - ), requestName); - - messageEngine.broadCastInteractionRequestToSessionWithTrainerIds(needConfirmation, interactionRequest.type, interactionRequest.trainerId, requestName, handshaker); - return this; - } - - private AkkamonNexus onTrainerOffline(RespondTrainerOffline trainerOfflineMsg) { - getContext().getLog().info("Removing {} from akkamon sessions!", trainerOfflineMsg.session.getTrainerId()); - messageEngine.removeTrainerSessionFromScene(trainerOfflineMsg.sceneId, trainerOfflineMsg.session); - return this; - } - - private AkkamonNexus onTrainerOfflineRequest(RequestTrainerOffline trainerOfflineRequest) { - ActorRef sceneTrainerGroup = sceneIdToActor.get( - trainerOfflineRequest.sceneId - ); - if (sceneTrainerGroup != null) { - sceneTrainerGroup.tell(trainerOfflineRequest); - } else { - getContext().getLog().info("Ignoring trainerOffline request in scene {}, it isn't mapped to a sceneTrainerActor.", trainerOfflineRequest.sceneId); - } - return this; - } - - private AkkamonNexus onHeartBeatQueryResponse(RespondHeartBeatQuery response) { - // Turn on for logging - - - // StringBuilder positions = new StringBuilder(); - // positions.append("\n" + response.sceneId.toUpperCase(Locale.ROOT) + "\n"); - // for (Map.Entry entry : response.trainerMovementQueues.entrySet()) { - // positions.append(entry.getKey() + ": " +entry.getValue()); - // positions.append("\n"); - // } - // getContext().getLog().info(String.valueOf(positions)); - - messageEngine.broadCastHeartBeatToScene(response.sceneId, response.trainerMovementQueues); - - return this; - } - - private AkkamonNexus onHeartBeat(RequestHeartBeat heartBeatRequest) { - // TODO do some checks here? - for (ActorRef sceneGroupActor: sceneIdToActor.values()) { - sceneGroupActor.tell(heartBeatRequest); - } - return this; - } - - private AkkamonNexus onNewTilePos(RequestNewTilePos newTilePosRequest) { - ActorRef sceneTrainerGroup = sceneIdToActor.get( - newTilePosRequest.sceneId - ); - if (sceneTrainerGroup != null) { - sceneTrainerGroup.tell(newTilePosRequest); - } else { - getContext().getLog().info("Ignoring newTilePos request in scene {}, it isn't mapped to a sceneTrainerActor.", newTilePosRequest.sceneId); - } - return this; - } - - private AkkamonNexus onStopMoving(RequestStopMoving stopMovingRequest) { - ActorRef sceneTrainerGroup = sceneIdToActor.get( - stopMovingRequest.sceneId - ); - if (sceneTrainerGroup != null) { - sceneTrainerGroup.tell(stopMovingRequest); - } else { - getContext().getLog().info("Ignoring stopMove request in scene {}, it isn't mapped to a sceneTrainerActor.", stopMovingRequest.sceneId); - } - return this; - } - - private AkkamonNexus onStartMoving(RequestStartMoving startMovingRequest) { - ActorRef sceneTrainerGroup = sceneIdToActor.get( - startMovingRequest.sceneId - ); - if (sceneTrainerGroup != null) { - sceneTrainerGroup.tell(startMovingRequest); - } else { - getContext().getLog().info("Ignoring startMove request in scene {}, it isn't mapped to a sceneTrainerActor.", startMovingRequest.sceneId); - } - return this; - } - - private AkkamonNexus onTrainerRegistered(TrainerRegistered reply) { - // TODO test when registration fails? - getContext().getLog().info("Adding {} to scene {} Live AkkamonSessions in Messaging Engine", reply.trainerId, reply.sceneId); - reply.session.setTrainerId(reply.trainerId); - messageEngine.registerTrainerSessionToSceneAndTrainerIdMaps(reply.sceneId, reply.session); - return this; - } - - private AkkamonNexus onTrainerRegistration(RequestTrainerRegistration registrationRequest) { - String sceneId = registrationRequest.sceneId; - String trainerId = registrationRequest.trainerId; - - getContext().getLog().info("Nexus received registration request for {} in {}", trainerId, sceneId); - - ActorRef sceneTrainerGroup = sceneIdToActor.get(sceneId); - if (sceneTrainerGroup != null) { - sceneTrainerGroup.tell(registrationRequest); - } else { - getContext().getLog().info("Creating sceneTrainerGroup {} for trainer {}", sceneId, trainerId); - ActorRef sceneActor = - getContext().spawn(SceneTrainerGroup.create(sceneId), "scene-" + sceneId); - - getContext().watchWith(sceneActor, new SceneTrainerGroupTerminated(sceneId)); - sceneActor.tell(registrationRequest); - sceneIdToActor.put(sceneId, sceneActor); - } - return this; - } - -} diff --git a/domain/src/main/java/akkamon/domain/AkkamonSession.java b/domain/src/main/java/akkamon/domain/AkkamonSession.java index 68aa1fc..d44e6db 100644 --- a/domain/src/main/java/akkamon/domain/AkkamonSession.java +++ b/domain/src/main/java/akkamon/domain/AkkamonSession.java @@ -1,9 +1,11 @@ package akkamon.domain; +import akkamon.domain.actors.AkkamonNexus; + public interface AkkamonSession { void send(String event); - void setTrainerId(String trainerId); + void settrainerID(AkkamonNexus.TrainerID trainerID); - String getTrainerId(); + AkkamonNexus.TrainerID gettrainerID(); } diff --git a/domain/src/main/java/akkamon/domain/HeartBeatQuery.java b/domain/src/main/java/akkamon/domain/HeartBeatQuery.java index aa201c3..45d6e38 100644 --- a/domain/src/main/java/akkamon/domain/HeartBeatQuery.java +++ b/domain/src/main/java/akkamon/domain/HeartBeatQuery.java @@ -3,10 +3,14 @@ package akkamon.domain; import akka.actor.typed.ActorRef; import akka.actor.typed.Behavior; import akka.actor.typed.javadsl.*; +import akkamon.domain.actors.AkkamonNexus; +import akkamon.domain.actors.Trainer; import java.time.Duration; import java.util.*; +import static akkamon.domain.actors.AkkamonNexus.*; + public class HeartBeatQuery extends AbstractBehavior { public interface Command {} @@ -26,15 +30,15 @@ public class HeartBeatQuery extends AbstractBehavior { } private static class TrainerOffline implements Command { - final String trainerId; + final TrainerID trainerID; - private TrainerOffline(String trainerId) { - this.trainerId = trainerId; + private TrainerOffline(TrainerID trainerID) { + this.trainerID = trainerID; } } public static Behavior create( - Map> trainerIdToActor, + Map> trainerIDToActor, long requestId, String sceneId, ActorRef requester, @@ -44,7 +48,7 @@ public class HeartBeatQuery extends AbstractBehavior { context -> Behaviors.withTimers( timers -> new HeartBeatQuery( - trainerIdToActor, + trainerIDToActor, requestId, sceneId, requester, @@ -59,11 +63,11 @@ public class HeartBeatQuery extends AbstractBehavior { private final long requestId; private final String sceneId; private final ActorRef requester; - private Map repliesSoFar = new HashMap(); - private final Set stillWaiting; + private Map repliesSoFar = new HashMap<>(); + private final Set stillWaiting; public HeartBeatQuery( - Map> trainerIdToActor, + Map> trainerIDToActor, long requestId, String sceneId, ActorRef requester, @@ -80,7 +84,7 @@ public class HeartBeatQuery extends AbstractBehavior { ActorRef respondTrainerPositionAdapter = context.messageAdapter(Trainer.RespondMovementQueue.class, WrappedRespondMovementQueue::new); - for (Map.Entry> entry : trainerIdToActor.entrySet()) { + for (Map.Entry> entry : trainerIDToActor.entrySet()) { context.watchWith(entry.getValue(), new TrainerOffline(entry.getKey())); entry.getValue().tell( new Trainer.ReadMovementQueue( @@ -88,7 +92,7 @@ public class HeartBeatQuery extends AbstractBehavior { respondTrainerPositionAdapter )); } - stillWaiting = new HashSet<>(trainerIdToActor.keySet()); + stillWaiting = new HashSet<>(trainerIDToActor.keySet()); } @Override @@ -99,25 +103,25 @@ public class HeartBeatQuery extends AbstractBehavior { } private Behavior onRespondMovementQueue(WrappedRespondMovementQueue r) { - AkkamonNexus.MovementQueueReading movementQueueRead = null; + MovementQueueReading movementQueueRead = null; if (r.response.value.size() != 0) { - movementQueueRead = new AkkamonNexus.MovementQueue(r.response.value); + movementQueueRead = new MovementQueue(r.response.value); } else { Queue queue = new LinkedList<>(); queue.add(Direction.NONE); - movementQueueRead = new AkkamonNexus.MovementQueue(queue); + movementQueueRead = new MovementQueue(queue); } - String trainerId = r.response.trainerId; - repliesSoFar.put(trainerId, movementQueueRead); - stillWaiting.remove(trainerId); + TrainerID trainerID = r.response.trainerID; + repliesSoFar.put(trainerID, movementQueueRead); + stillWaiting.remove(trainerID); return respondWhenAllCollected(); } private Behavior respondWhenAllCollected() { if (stillWaiting.isEmpty()) { - requester.tell(new AkkamonNexus.RespondHeartBeatQuery( + requester.tell(new RespondHeartBeatQuery( requestId, sceneId, repliesSoFar)); diff --git a/domain/src/main/java/akkamon/domain/InteractionHandshaker.java b/domain/src/main/java/akkamon/domain/InteractionHandshaker.java index 43c0c2e..9817be1 100644 --- a/domain/src/main/java/akkamon/domain/InteractionHandshaker.java +++ b/domain/src/main/java/akkamon/domain/InteractionHandshaker.java @@ -3,12 +3,15 @@ package akkamon.domain; import akka.actor.typed.ActorRef; import akka.actor.typed.Behavior; import akka.actor.typed.javadsl.*; +import akkamon.domain.actors.AkkamonNexus; import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Set; +import static akkamon.domain.actors.AkkamonNexus.*; + public class InteractionHandshaker extends AbstractBehavior { public interface Command { @@ -21,23 +24,20 @@ public class InteractionHandshaker extends AbstractBehavior create( - String trainerId, + TrainerID trainerID, String type, - List needingConfirmation, - String sceneId, + List needingConfirmation, String requestName, ActorRef replyTo, Duration timeout) { @@ -46,10 +46,9 @@ public class InteractionHandshaker extends AbstractBehavior Behaviors.withTimers( timers -> new InteractionHandshaker( context, - trainerId, + trainerID, type, new HashSet(needingConfirmation), - sceneId, requestName, replyTo, timeout, @@ -58,20 +57,19 @@ public class InteractionHandshaker extends AbstractBehavior stillWaiting; + private Set stillWaiting; private ActorRef replyTo; private String requestName; private String type; - private Set waitingToStartInteraction = new HashSet<>(); + private Set waitingToStartInteraction = new HashSet<>(); public InteractionHandshaker(ActorContext context, - String trainerId, + TrainerID trainerID, String type, - Set needingToShakeHands, - String sceneId, + Set needingToShakeHands, String requestName, ActorRef replyTo, Duration timeout, @@ -85,7 +83,7 @@ public class InteractionHandshaker extends AbstractBehavior onReply(InteractionReply r) { - getContext().getLog().info("received reply from {} with value {}!", r.trainerId, r.value); - stillWaiting.remove(r.trainerId); - this.waitingToStartInteraction.add(r.trainerId); + getContext().getLog().info("received reply from {} with value {}!", r.trainerID, r.value); + getContext().getLog().info(String.valueOf(stillWaiting)); + stillWaiting.remove(r.trainerID); + getContext().getLog().info(String.valueOf(stillWaiting)); + this.waitingToStartInteraction.add(r.trainerID); if (r.value) { return respondIfAllRepliesReceived(); } else { - replyTo.tell(new AkkamonNexus.RespondInteractionHandshaker( + replyTo.tell(new RespondInteractionHandshaker( requestName, type, HandshakeResult.FAIL, @@ -130,8 +130,10 @@ public class InteractionHandshaker extends AbstractBehavior respondIfAllRepliesReceived() { + getContext().getLog().info(String.valueOf(stillWaiting)); if (this.stillWaiting.isEmpty()) { - replyTo.tell(new AkkamonNexus.RespondInteractionHandshaker( + getContext().getLog().info("Sending out interaction Start!"); + replyTo.tell(new RespondInteractionHandshaker( requestName, type, HandshakeResult.SUCCESS, diff --git a/domain/src/main/java/akkamon/domain/InteractionTypes.java b/domain/src/main/java/akkamon/domain/InteractionTypes.java new file mode 100644 index 0000000..b915893 --- /dev/null +++ b/domain/src/main/java/akkamon/domain/InteractionTypes.java @@ -0,0 +1,5 @@ +package akkamon.domain; + +public enum InteractionTypes { + BATTLE +} diff --git a/domain/src/main/java/akkamon/domain/SceneTrainerGroup.java b/domain/src/main/java/akkamon/domain/SceneTrainerGroup.java deleted file mode 100644 index e8ace82..0000000 --- a/domain/src/main/java/akkamon/domain/SceneTrainerGroup.java +++ /dev/null @@ -1,239 +0,0 @@ -package akkamon.domain; - -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.AbstractBehavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Receive; - -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; - -public class SceneTrainerGroup extends AbstractBehavior { - - public interface Command { } - - public static class TrainerOffline - implements Command, AkkamonNexus.Command { - public ActorRef trainer; - public String sceneId; - public String trainerId; - public ActorRef replyTo; - - public TrainerOffline(ActorRef trainerActor, String sceneId, String trainerId, ActorRef replyTo) { - this.trainer = trainerActor; - this.sceneId = sceneId; - this.trainerId = trainerId; - this.replyTo = replyTo; - } - } - - public static Behavior create(String TrainerGroupId) { - return Behaviors.setup(context -> new SceneTrainerGroup(context, TrainerGroupId)); - } - - private final String sceneId; - private final Map> trainerIdToActor= new HashMap(); - - public SceneTrainerGroup(ActorContext context, String sceneId) { - super(context); - - this.sceneId = sceneId; - - getContext().getLog().info("SceneTrainerGroup Actor {} started", sceneId); - } - - @Override - public Receive createReceive() { - return newReceiveBuilder() - .onMessage( - AkkamonNexus.RequestTrainerRegistration.class, - this::onTrainerRegistration - ) - .onMessage( - AkkamonNexus.RequestTrainerOffline.class, - this::onTrainerOfflineRequest - ) - .onMessage( - TrainerOffline.class, - this::onWatchedTrainerOffline - ) - .onMessage( - AkkamonNexus.RequestStartMoving.class, - this::onStartMoving - ) - .onMessage( - AkkamonNexus.RequestStopMoving.class, - this::onStopMoving - ) - .onMessage( - AkkamonNexus.RequestNewTilePos.class, - this::onNewTilePos - ) - .onMessage( - AkkamonNexus.RequestHeartBeat.class, - this::onHeartBeat - ) - .build(); - } - - private SceneTrainerGroup onWatchedTrainerOffline(TrainerOffline trainerOfflineMsg) { - trainerOfflineMsg.replyTo.tell(trainerOfflineMsg); - trainerIdToActor.remove(trainerOfflineMsg.trainerId); - return this; - } - - private SceneTrainerGroup onTrainerOfflineRequest(AkkamonNexus.RequestTrainerOffline trainerOfflineRequest) { - if (this.sceneId.equals(trainerOfflineRequest.sceneId)) { - ActorRef trainerActor = trainerIdToActor.get(trainerOfflineRequest.trainerId); - if (trainerActor != null) { - trainerActor.tell(trainerOfflineRequest); - trainerOfflineRequest.replyTo.tell(new AkkamonNexus.RespondTrainerOffline( - trainerOfflineRequest.requestId, - trainerOfflineRequest.sceneId, - trainerOfflineRequest.session - )); - } else { - getContext() - .getLog() - .warn( - "Ignoring trainerOffline for trainerId {}. There is no actor mapped to it.", - trainerOfflineRequest.trainerId - ); - } - } else { - getContext() - .getLog() - .warn( - "Ignoring trainerOffline for {}. This actor is responsible for {}.", - trainerOfflineRequest.sceneId, - this.sceneId); - } - return this; - } - - private SceneTrainerGroup onHeartBeat(AkkamonNexus.RequestHeartBeat heartBeatRequest) { - Map> trainerIdToActorCopy = new HashMap<>(this.trainerIdToActor); - getContext() - .spawnAnonymous( - HeartBeatQuery.create( - trainerIdToActorCopy, - heartBeatRequest.requestId, - sceneId, - heartBeatRequest.replyTo, - Duration.ofSeconds(3) - ) - ); - return this; - } - - private SceneTrainerGroup onNewTilePos(AkkamonNexus.RequestNewTilePos newTilePosRequest) { - if (this.sceneId.equals(newTilePosRequest.sceneId)) { - ActorRef trainerActor = trainerIdToActor.get(newTilePosRequest.trainerId); - if (trainerActor != null) { - trainerActor.tell(newTilePosRequest); - } else { - getContext() - .getLog() - .warn( - "Ignoring newTilePos for trainerId {}. There is no actor mapped to it.", - newTilePosRequest.trainerId - ); - } - } else { - getContext() - .getLog() - .warn( - "Ignoring newTilePos for {}. This actor is responsible for {}.", - newTilePosRequest.sceneId, - this.sceneId); - } - return this; - } - - private SceneTrainerGroup onStopMoving(AkkamonNexus.RequestStopMoving stopMovingRequest) { - if (this.sceneId.equals(stopMovingRequest.sceneId)) { - ActorRef trainerActor = trainerIdToActor.get(stopMovingRequest.trainerId); - if (trainerActor != null) { - trainerActor.tell(stopMovingRequest); - } else { - getContext() - .getLog() - .warn( - "Ignoring stopMovingRequest for trainerId {}. There is no actor mapped to it.", - stopMovingRequest.trainerId - ); - } - } else { - getContext() - .getLog() - .warn( - "Ignoring stopMovingRequest for {}. This actor is responsible for {}.", - stopMovingRequest.sceneId, - this.sceneId); - } - return this; - } - - private SceneTrainerGroup onStartMoving(AkkamonNexus.RequestStartMoving startMovingRequest) { - if (this.sceneId.equals(startMovingRequest.sceneId)) { - ActorRef trainerActor = trainerIdToActor.get(startMovingRequest.trainerId); - if (trainerActor != null) { - trainerActor.tell(startMovingRequest); - } else { - getContext() - .getLog() - .warn( - "Ignoring startMovingRequest for trainerId {}. There is no actor mapped to it.", - startMovingRequest.trainerId - ); - } - } else { - getContext() - .getLog() - .warn( - "Ignoring startMovingRequest for {}. This actor is responsible for {}.", - startMovingRequest.sceneId, - this.sceneId); - } - return this; - } - - private SceneTrainerGroup onTrainerRegistration(AkkamonNexus.RequestTrainerRegistration registrationRequest) { - if (this.sceneId.equals(registrationRequest.sceneId)) { - ActorRef trainerActor = trainerIdToActor.get(registrationRequest.trainerId); - if (trainerActor != null) { - // TODO add optional already registered? - registrationRequest.replyTo.tell(new AkkamonNexus.TrainerRegistered( - registrationRequest.trainerId, - sceneId, - registrationRequest.session - )); - } else { - getContext().getLog().info("Creating trainer actor for {}", registrationRequest.trainerId); - trainerActor = - getContext() - .spawn(Trainer.create(sceneId, registrationRequest.trainerId), "trainer-" + registrationRequest.trainerId); - getContext() - .watchWith(trainerActor, new SceneTrainerGroup.TrainerOffline(trainerActor, sceneId, registrationRequest.trainerId, registrationRequest.replyTo)); - trainerIdToActor.put(registrationRequest.trainerId, trainerActor); - registrationRequest.replyTo.tell(new AkkamonNexus.TrainerRegistered( - registrationRequest.trainerId, - sceneId, - registrationRequest.session - )); - } - } else { - getContext() - .getLog() - .warn( - "Ignoring TrainerRegistration request for {}. This actor is responsible for {}.", - registrationRequest.sceneId, - this.sceneId); - } - return this; - } - -} diff --git a/domain/src/main/java/akkamon/domain/Trainer.java b/domain/src/main/java/akkamon/domain/Trainer.java deleted file mode 100644 index caad860..0000000 --- a/domain/src/main/java/akkamon/domain/Trainer.java +++ /dev/null @@ -1,126 +0,0 @@ -package akkamon.domain; - -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.AbstractBehavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Receive; - -import java.util.LinkedList; -import java.util.Optional; -import java.util.Queue; - -public class Trainer extends AbstractBehavior { - - public interface Command { } - - public static class ReadMovementQueue implements Command { - final long requestId; - final ActorRef replyTo; - - public ReadMovementQueue(long requestId, ActorRef replyTo) { - this.requestId = requestId; - this.replyTo = replyTo; - } - } - - public static final class RespondMovementQueue { - final long requestId; - final String trainerId; - final Queue value; - - public RespondMovementQueue( - long requestId, - String trainerId, - Queue value - ) { - this.requestId = requestId; - this.trainerId = trainerId; - this.value = value; - } - } - - public static Behavior create(String sceneId, String trainerId) { - return Behaviors.setup(context -> new Trainer(context, sceneId, trainerId)); - } - - private String sceneId; - private String trainerId; - - private Queue movementQueue = new LinkedList<>(); - - private Direction movementDirection = Direction.NONE; - - private Optional lastValidTilePos = Optional.empty(); - - public Trainer(ActorContext context, String sceneId, String trainerId) { - super(context); - this.sceneId = sceneId; - this.trainerId = trainerId; - } - - @Override - public Receive createReceive() { - return newReceiveBuilder() - .onMessage( - ReadMovementQueue.class, - this::onReadMovementQueue - ) - .onMessage( - AkkamonNexus.RequestTrainerOffline.class, - this::onTrainerOffline - ) - .onMessage( - AkkamonNexus.RequestStartMoving.class, - this::onStartMoving - ) - .onMessage( - AkkamonNexus.RequestStopMoving.class, - this::onStopMoving) - .onMessage( - AkkamonNexus.RequestNewTilePos.class, - this::onNewTilePos - ) - .build(); - } - - private Behavior onTrainerOffline(AkkamonNexus.RequestTrainerOffline trainerOfflineRequest) { - getContext().getLog().info("Trainer {} went offline, the actor has stopped! My supervisor should handle closing my connection!"); - return Behaviors.stopped(); - } - - private Trainer onReadMovementQueue(ReadMovementQueue readTrainerPositionRequest) { - readTrainerPositionRequest.replyTo.tell(new RespondMovementQueue( - readTrainerPositionRequest.requestId, - trainerId, - new LinkedList<>(movementQueue) - )); - this.movementQueue.clear(); - return this; - } - - private Trainer onNewTilePos(AkkamonNexus.RequestNewTilePos newTilePosRequest) { - // getContext().getLog().info("Trainer {} has new {}.", trainerId, newTilePosRequest.tilePos); - if (isMoving()) { - this.movementQueue.add(this.movementDirection); - } - return this; - } - - private Trainer onStopMoving(AkkamonNexus.RequestStopMoving stopMovingRequest) { - // getContext().getLog().info("Trainer {} stops to move {}.", trainerId, stopMovingRequest.direction); - this.movementDirection = Direction.NONE; - return this; - } - - private Trainer onStartMoving(AkkamonNexus.RequestStartMoving startMovingRequest) { - // getContext().getLog().info("Trainer {} starts to move {}.", trainerId, startMovingRequest.direction); - this.movementDirection = startMovingRequest.direction; - return this; - } - - private boolean isMoving() { - return this.movementDirection != Direction.NONE; - } -} diff --git a/domain/src/main/java/akkamon/domain/actors/AkkamonBattle.java b/domain/src/main/java/akkamon/domain/actors/AkkamonBattle.java new file mode 100644 index 0000000..6f65845 --- /dev/null +++ b/domain/src/main/java/akkamon/domain/actors/AkkamonBattle.java @@ -0,0 +1,36 @@ +package akkamon.domain.actors; + +import akka.actor.typed.ActorRef; +import akka.actor.typed.javadsl.ActorContext; +import akka.actor.typed.Behavior; +import akka.actor.typed.javadsl.AbstractBehavior; +import akka.actor.typed.javadsl.Behaviors; +import akka.actor.typed.javadsl.Receive; + +public class AkkamonBattle extends AbstractBehavior { + + public interface Command { } + + public static class BattleCreatedResponse implements AkkamonNexus.Command { + + } + + public static Behavior create( + ActorRef replyTo + ) { + return Behaviors.setup( + context -> new AkkamonBattle(context) + ); + } + + public AkkamonBattle(ActorContext context) { + super(context); + } + + + @Override + public Receive createReceive() { + return null; + } + +} diff --git a/domain/src/main/java/akkamon/domain/actors/AkkamonNexus.java b/domain/src/main/java/akkamon/domain/actors/AkkamonNexus.java new file mode 100644 index 0000000..e560a69 --- /dev/null +++ b/domain/src/main/java/akkamon/domain/actors/AkkamonNexus.java @@ -0,0 +1,447 @@ +package akkamon.domain.actors; + +import akka.actor.typed.ActorRef; +import akka.actor.typed.Behavior; +import akka.actor.typed.javadsl.AbstractBehavior; +import akka.actor.typed.javadsl.ActorContext; +import akka.actor.typed.javadsl.Behaviors; +import akka.actor.typed.javadsl.Receive; +import akkamon.domain.*; + +import java.time.Duration; +import java.util.*; + +public class AkkamonNexus extends AbstractBehavior { + + + public interface Command {} + + public static class TrainerID { + public String id; + public String scene; + + public TrainerID(String id, String scene) { + this.id = id; + this.scene = scene; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TrainerID trainerID = (TrainerID) o; + return Objects.equals(id, trainerID.id) && Objects.equals(scene, trainerID.scene); + } + + @Override + public int hashCode() { + return Objects.hash(id, scene); + } + + @Override + public String toString() { + return "{ " + + "\"id\": \"" + id + "\"," + + "\"scene\": \"" + scene + "\"" + + " }"; + } + } + + public static class BattleStart implements SceneTrainerGroup.Command, Trainer.Command { + + } + + public static class RespondInteractionHandshaker implements Command { + public String requestName; + public String interactionType; + public InteractionHandshaker.HandshakeResult result; + public Set waitingToStartInteraction; + + + public RespondInteractionHandshaker(String requestName, + String interactionType, + InteractionHandshaker.HandshakeResult result, + Set waitingToStartInteraction) { + this.requestName = requestName; + this.interactionType = interactionType; + this.result = result; + this.waitingToStartInteraction = waitingToStartInteraction; + } + } + + public static class RequestInteraction + implements Command { + + public long requestId; + public String type; + public String sceneId; + public TrainerID trainerID; + public List forwardTo; + public ActorRef replyTo; + + public RequestInteraction(long requestId, String type, TrainerID trainerID, List forwardTo, ActorRef replyTo) { + this.requestId = requestId; + this.type = type; + this.trainerID = trainerID; + this.forwardTo = forwardTo; + this.replyTo = replyTo; + } + } + + public static class RequestTrainerRegistration + implements AkkamonNexus.Command, SceneTrainerGroup.Command { + public String trainerName; + public String sceneId; + public AkkamonSession session; + public ActorRef replyTo; + + public RequestTrainerRegistration( + String trainerName, + String sceneId, + AkkamonSession session, + ActorRef replyTo + ) { + this.trainerName = trainerName; + this.sceneId = sceneId; + this.session = session; + this.replyTo = replyTo; + } + } + + public static class TrainerRegistered implements Command { + private TrainerID trainerID; + private AkkamonSession session; + + public TrainerRegistered( + TrainerID trainerID, + AkkamonSession session + ) { + this.trainerID = trainerID; + this.session = session; + } + } + + public static class RequestStartMoving + implements Command, SceneTrainerGroup.Command, Trainer.Command { + public long requestId; + public TrainerID trainerID; + public Direction direction; + public ActorRef replyTo; + + public RequestStartMoving(long requestId, AkkamonNexus.TrainerID trainerID, Direction direction, ActorRef replyTo) { + this.requestId = requestId; + this.trainerID = trainerID; + this.direction = direction; + this.replyTo = replyTo; + } + } + + public static class RequestStopMoving + implements Command, SceneTrainerGroup.Command, Trainer.Command { + public long requestId; + public TrainerID trainerID; + public Direction direction; + public ActorRef replyTo; + + public RequestStopMoving( + long requestId, + TrainerID trainerID, + Direction direction, + ActorRef replyTo) { + this.requestId = requestId; + this.trainerID = trainerID; + this.direction = direction; + this.replyTo = replyTo; + } + } + + public static class RequestNewTilePos + implements Command, SceneTrainerGroup.Command, Trainer.Command { + public long requestId; + public TrainerID trainerID; + public TilePos tilePos; + public ActorRef replyTo; + + public RequestNewTilePos(long requestId, TrainerID trainerID, TilePos tilePos, ActorRef replyTo) { + this.requestId = requestId; + this.trainerID = trainerID; + this.tilePos = tilePos; + this.replyTo = replyTo; + } + } + + public static class RequestHeartBeat + implements Command, SceneTrainerGroup.Command { + + public long requestId; + // TODO find a way to make the command Narrower + public ActorRef replyTo; + + public RequestHeartBeat(long requestId, ActorRef replyTo) { + this.requestId = requestId; + this.replyTo = replyTo; + } + } + + private static class SceneTrainerGroupTerminated implements AkkamonNexus.Command { + public SceneTrainerGroupTerminated(String sceneId) { + } + } + + public static class RequestTrainerOffline + implements Command, SceneTrainerGroup.Command, Trainer.Command { + public long requestId; + public TrainerID trainerID; + public AkkamonSession session; + public ActorRef replyTo; + + public RequestTrainerOffline(long requestId, TrainerID trainerID, String sceneId, AkkamonSession session, ActorRef replyTo) { + this.requestId = requestId; + this.trainerID = trainerID; + this.session = session; + this.replyTo = replyTo; + } + } + + public static class RespondTrainerOffline + implements Command { + public long requestId; + public TrainerID trainerID; + public AkkamonSession session; + + public RespondTrainerOffline(long requestId, TrainerID trainerID, AkkamonSession session) { + this.requestId = requestId; + this.trainerID = trainerID; + this.session = session; + } + } + + public static class RespondHeartBeatQuery implements Command { + + public final long requestId; + public final String sceneId; + public final Map trainerMovementQueues; + + public RespondHeartBeatQuery( + long requestId, + String sceneId, + Map trainerPositions) { + this.requestId = requestId; + this.sceneId = sceneId; + this.trainerMovementQueues = trainerPositions; + } + } + + public interface MovementQueueReading { } + + public static class MovementQueue implements MovementQueueReading { + public final Queue value; + + public MovementQueue(Queue value) { + this.value = value; + } + } + + public enum MovementQueueEmpty implements MovementQueueReading { + INSTANCE + } + + public enum TrainerOffline implements MovementQueueReading { + INSTANCE + } + + public enum TrainerTimedOut implements MovementQueueReading { + INSTANCE + } + + public static Behavior create(AkkamonMessageEngine messagingEngine) { + return Behaviors.setup(context -> new AkkamonNexus(context, messagingEngine)); + } + + private AkkamonMessageEngine messageEngine; + private Map> sceneIdToActor = new HashMap<>(); + + public AkkamonNexus(ActorContext context, AkkamonMessageEngine msgEngine) { + super(context); + this.messageEngine = msgEngine; + getContext().getLog().info("AkkamonNexus is up and running, waiting eagerly for your messages!"); + } + + @Override + public Receive createReceive() { + return newReceiveBuilder() + .onMessage(RequestTrainerRegistration.class, this::onTrainerRegistration) + .onMessage(TrainerRegistered.class, this::onTrainerRegistered) + + .onMessage(RequestTrainerOffline.class, this::onTrainerOfflineRequest) + .onMessage(RespondTrainerOffline.class, this::onTrainerOffline) + + .onMessage(RequestHeartBeat.class, this::onHeartBeat) + .onMessage(RespondHeartBeatQuery.class, this::onHeartBeatQueryResponse) + + .onMessage(RequestStartMoving.class, this::onStartMoving) + .onMessage(RequestStopMoving.class, this::onStopMoving) + .onMessage(RequestNewTilePos.class, this::onNewTilePos) + + .onMessage(RequestInteraction.class, this::onInteractionRequest) + .onMessage(RespondInteractionHandshaker.class, this::onInteractionHandshakerResponse) + + .onMessage(AkkamonBattle.BattleCreatedResponse.class, this::onBattleCreatedResponse) + .build(); + } + + private AkkamonNexus onBattleCreatedResponse(AkkamonBattle.BattleCreatedResponse r) { + getContext().getLog().info("Created battle between {} and {}, they should now only be listening to battle commands!"); + return this; + } + + private Behavior onInteractionHandshakerResponse(RespondInteractionHandshaker r) { + this.messageEngine.removeInteractionHandshaker(r.requestName); + + if (r.result.equals(InteractionHandshaker.HandshakeResult.SUCCESS)) { + messageEngine.broadCastInteractionStart(r.requestName, r.interactionType, r.waitingToStartInteraction); + + switch (r.interactionType) { + case "battle": + getContext().spawn(AkkamonBattle.create( + getContext().getSelf() + ), + r.requestName); + break; + } + + } else if (r.result.equals(InteractionHandshaker.HandshakeResult.FAIL)) { + messageEngine.broadCastHandshakeFail(r.requestName, r.waitingToStartInteraction); + } + return this; + } + + private AkkamonNexus onInteractionRequest(RequestInteraction interactionRequest) { + List needConfirmation = interactionRequest.forwardTo; + + getContext().getLog().info("Creating interactionHandshaker of type {} from {} to {} ", interactionRequest.type, interactionRequest.trainerID, needConfirmation); + + String requestName = "interaction-handshaker-" + interactionRequest.type + "-" + interactionRequest.trainerID.id + "-" + interactionRequest.requestId; + + ActorRef handshaker = getContext().spawn(InteractionHandshaker.create( + interactionRequest.trainerID, + interactionRequest.type, + interactionRequest.forwardTo, + requestName, + interactionRequest.replyTo, + Duration.ofSeconds(60) + ), requestName); + + messageEngine.broadCastInteractionRequestToSessionWithtrainerIDs(new ArrayList<>(needConfirmation), interactionRequest.type, interactionRequest.trainerID, requestName, handshaker); + return this; + } + + private AkkamonNexus onTrainerOffline(RespondTrainerOffline trainerOfflineMsg) { + getContext().getLog().info("Removing {} from akkamon sessions!", trainerOfflineMsg.session.gettrainerID()); + messageEngine.removeTrainerSessionFromScene(trainerOfflineMsg.trainerID, trainerOfflineMsg.session); + return this; + } + + private AkkamonNexus onTrainerOfflineRequest(RequestTrainerOffline trainerOfflineRequest) { + ActorRef sceneTrainerGroup = sceneIdToActor.get( + trainerOfflineRequest.trainerID.scene + ); + if (sceneTrainerGroup != null) { + sceneTrainerGroup.tell(trainerOfflineRequest); + } else { + getContext().getLog().info("Ignoring trainerOffline request in scene {}, it isn't mapped to a sceneTrainerActor.", trainerOfflineRequest.trainerID.scene); + } + return this; + } + + private AkkamonNexus onHeartBeatQueryResponse(RespondHeartBeatQuery response) { + // Turn on for logging + + + // StringBuilder positions = new StringBuilder(); + // positions.append("\n" + response.sceneId.toUpperCase(Locale.ROOT) + "\n"); + // for (Map.Entry entry : response.trainerMovementQueues.entrySet()) { + // positions.append(entry.getKey() + ": " +entry.getValue()); + // positions.append("\n"); + // } + // getContext().getLog().info(String.valueOf(positions)); + + messageEngine.broadCastHeartBeatToScene(response.sceneId, response.trainerMovementQueues); + + return this; + } + + private AkkamonNexus onHeartBeat(RequestHeartBeat heartBeatRequest) { + // TODO do some checks here? + for (ActorRef sceneGroupActor: sceneIdToActor.values()) { + sceneGroupActor.tell(heartBeatRequest); + } + return this; + } + + private AkkamonNexus onNewTilePos(RequestNewTilePos newTilePosRequest) { + ActorRef sceneTrainerGroup = sceneIdToActor.get( + newTilePosRequest.trainerID.scene + ); + if (sceneTrainerGroup != null) { + sceneTrainerGroup.tell(newTilePosRequest); + } else { + getContext().getLog().info("Ignoring newTilePos request in scene {}, it isn't mapped to a sceneTrainerActor.", newTilePosRequest.trainerID.scene); + } + return this; + } + + private AkkamonNexus onStopMoving(RequestStopMoving stopMovingRequest) { + ActorRef sceneTrainerGroup = sceneIdToActor.get( + stopMovingRequest.trainerID.scene + ); + if (sceneTrainerGroup != null) { + sceneTrainerGroup.tell(stopMovingRequest); + } else { + getContext().getLog().info("Ignoring stopMove request in scene {}, it isn't mapped to a sceneTrainerActor.", stopMovingRequest.trainerID.scene); + } + return this; + } + + private AkkamonNexus onStartMoving(RequestStartMoving startMovingRequest) { + ActorRef sceneTrainerGroup = sceneIdToActor.get( + startMovingRequest.trainerID.scene + ); + if (sceneTrainerGroup != null) { + sceneTrainerGroup.tell(startMovingRequest); + } else { + getContext().getLog().info("Ignoring startMove request in scene {}, it isn't mapped to a sceneTrainerActor.", startMovingRequest.trainerID.scene); + } + return this; + } + + private AkkamonNexus onTrainerRegistered(TrainerRegistered reply) { + // TODO test when registration fails? + getContext().getLog().info("Adding {} to scene {} Live AkkamonSessions in Messaging Engine", reply.trainerID, reply.trainerID.scene); + reply.session.settrainerID(reply.trainerID); + messageEngine.registerTrainerSessionToSceneAndtrainerIDMaps(reply.trainerID, reply.session); + return this; + } + + private AkkamonNexus onTrainerRegistration(RequestTrainerRegistration registrationRequest) { + String sceneId = registrationRequest.sceneId; + String trainerName = registrationRequest.trainerName; + + getContext().getLog().info("Nexus received registration request for {} in {}", trainerName, sceneId); + + ActorRef sceneTrainerGroup = sceneIdToActor.get(sceneId); + if (sceneTrainerGroup != null) { + sceneTrainerGroup.tell(registrationRequest); + } else { + getContext().getLog().info("Creating sceneTrainerGroup {} for trainer {}", sceneId, trainerName); + ActorRef sceneActor = + getContext().spawn(SceneTrainerGroup.create(sceneId), "scene-" + sceneId); + + getContext().watchWith(sceneActor, new SceneTrainerGroupTerminated(sceneId)); + sceneActor.tell(registrationRequest); + sceneIdToActor.put(sceneId, sceneActor); + } + return this; + } + +} diff --git a/domain/src/main/java/akkamon/domain/actors/SceneTrainerGroup.java b/domain/src/main/java/akkamon/domain/actors/SceneTrainerGroup.java new file mode 100644 index 0000000..31f53b5 --- /dev/null +++ b/domain/src/main/java/akkamon/domain/actors/SceneTrainerGroup.java @@ -0,0 +1,243 @@ +package akkamon.domain.actors; + +import akka.actor.typed.ActorRef; +import akka.actor.typed.Behavior; +import akka.actor.typed.javadsl.AbstractBehavior; +import akka.actor.typed.javadsl.ActorContext; +import akka.actor.typed.javadsl.Behaviors; +import akka.actor.typed.javadsl.Receive; +import akkamon.domain.HeartBeatQuery; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +public class SceneTrainerGroup extends AbstractBehavior { + + public interface Command { } + + public static class TrainerOffline + implements Command, AkkamonNexus.Command { + public ActorRef trainer; + public String sceneId; + public String trainerID; + public ActorRef replyTo; + + public TrainerOffline(ActorRef trainerActor, String sceneId, String trainerID, ActorRef replyTo) { + this.trainer = trainerActor; + this.sceneId = sceneId; + this.trainerID = trainerID; + this.replyTo = replyTo; + } + } + + public static Behavior create(String TrainerGroupId) { + return Behaviors.setup(context -> new SceneTrainerGroup(context, TrainerGroupId)); + } + + private final String sceneId; + private final Map> trainerIDToActor= new HashMap(); + + public SceneTrainerGroup(ActorContext context, String sceneId) { + super(context); + + this.sceneId = sceneId; + + getContext().getLog().info("SceneTrainerGroup Actor {} started", sceneId); + } + + @Override + public Receive createReceive() { + return newReceiveBuilder() + .onMessage( + AkkamonNexus.RequestTrainerRegistration.class, + this::onTrainerRegistration + ) + .onMessage( + AkkamonNexus.RequestTrainerOffline.class, + this::onTrainerOfflineRequest + ) + .onMessage( + TrainerOffline.class, + this::onWatchedTrainerOffline + ) + .onMessage( + AkkamonNexus.RequestStartMoving.class, + this::onStartMoving + ) + .onMessage( + AkkamonNexus.RequestStopMoving.class, + this::onStopMoving + ) + .onMessage( + AkkamonNexus.RequestNewTilePos.class, + this::onNewTilePos + ) + .onMessage( + AkkamonNexus.RequestHeartBeat.class, + this::onHeartBeat + ) + .build(); + } + + private SceneTrainerGroup onWatchedTrainerOffline(TrainerOffline trainerOfflineMsg) { + trainerOfflineMsg.replyTo.tell(trainerOfflineMsg); + trainerIDToActor.remove(trainerOfflineMsg.trainerID); + return this; + } + + private SceneTrainerGroup onTrainerOfflineRequest(AkkamonNexus.RequestTrainerOffline trainerOfflineRequest) { + if (this.sceneId.equals(trainerOfflineRequest.trainerID.scene)) { + ActorRef trainerActor = trainerIDToActor.get(trainerOfflineRequest.trainerID); + if (trainerActor != null) { + trainerActor.tell(trainerOfflineRequest); + trainerOfflineRequest.replyTo.tell(new AkkamonNexus.RespondTrainerOffline( + trainerOfflineRequest.requestId, + trainerOfflineRequest.trainerID, + trainerOfflineRequest.session + )); + } else { + getContext() + .getLog() + .warn( + "Ignoring trainerOffline for trainerID {}. There is no actor mapped to it.", + trainerOfflineRequest.trainerID + ); + } + } else { + getContext() + .getLog() + .warn( + "Ignoring trainerOffline for {}. This actor is responsible for {}.", + trainerOfflineRequest.trainerID.scene, + this.sceneId); + } + return this; + } + + private SceneTrainerGroup onHeartBeat(AkkamonNexus.RequestHeartBeat heartBeatRequest) { + Map> trainerIDToActorCopy = new HashMap<>(this.trainerIDToActor); + getContext() + .spawnAnonymous( + HeartBeatQuery.create( + trainerIDToActorCopy, + heartBeatRequest.requestId, + sceneId, + heartBeatRequest.replyTo, + Duration.ofSeconds(3) + ) + ); + return this; + } + + private SceneTrainerGroup onNewTilePos(AkkamonNexus.RequestNewTilePos newTilePosRequest) { + if (this.sceneId.equals(newTilePosRequest.trainerID.scene)) { + ActorRef trainerActor = trainerIDToActor.get(newTilePosRequest.trainerID); + if (trainerActor != null) { + trainerActor.tell(newTilePosRequest); + } else { + getContext() + .getLog() + .warn( + "Ignoring newTilePos for trainerID {}. There is no actor mapped to it.", + newTilePosRequest.trainerID + ); + } + } else { + getContext() + .getLog() + .warn( + "Ignoring newTilePos for {}. This actor is responsible for {}.", + newTilePosRequest.trainerID.scene, + this.sceneId); + } + return this; + } + + private SceneTrainerGroup onStopMoving(AkkamonNexus.RequestStopMoving stopMovingRequest) { + if (this.sceneId.equals(stopMovingRequest.trainerID.scene)) { + ActorRef trainerActor = trainerIDToActor.get(stopMovingRequest.trainerID); + if (trainerActor != null) { + trainerActor.tell(stopMovingRequest); + } else { + getContext() + .getLog() + .warn( + "Ignoring stopMovingRequest for trainerID {}. There is no actor mapped to it.", + stopMovingRequest.trainerID + ); + } + } else { + getContext() + .getLog() + .warn( + "Ignoring stopMovingRequest for {}. This actor is responsible for {}.", + stopMovingRequest.trainerID, + this.sceneId); + } + return this; + } + + private SceneTrainerGroup onStartMoving(AkkamonNexus.RequestStartMoving startMovingRequest) { + if (this.sceneId.equals(startMovingRequest.trainerID.scene)) { + ActorRef trainerActor = trainerIDToActor.get(startMovingRequest.trainerID); + if (trainerActor != null) { + trainerActor.tell(startMovingRequest); + } else { + getContext() + .getLog() + .warn( + "Ignoring startMovingRequest for trainerID {}. There is no actor mapped to it.", + startMovingRequest.trainerID + ); + } + } else { + getContext() + .getLog() + .warn( + "Ignoring startMovingRequest for {}. This actor is responsible for {}.", + startMovingRequest.trainerID.scene, + this.sceneId); + } + return this; + } + + private SceneTrainerGroup onTrainerRegistration(AkkamonNexus.RequestTrainerRegistration registrationRequest) { + + AkkamonNexus.TrainerID existingOrNewTrainerID = new AkkamonNexus.TrainerID(registrationRequest.trainerName, this.sceneId); + + if (this.sceneId.equals(registrationRequest.sceneId)) { + ActorRef trainerActor = trainerIDToActor.get(existingOrNewTrainerID); + if (trainerActor != null) { + // TODO add optional already registered? + registrationRequest.replyTo.tell(new AkkamonNexus.TrainerRegistered( + existingOrNewTrainerID, + registrationRequest.session + )); + } else { + getContext().getLog().info("Creating trainer actor for {}", registrationRequest.trainerName); + trainerActor = + getContext() + .spawn(Trainer.create(existingOrNewTrainerID), "trainer-" + existingOrNewTrainerID.id); + getContext() + .watchWith(trainerActor, new TrainerOffline(trainerActor, sceneId, registrationRequest.trainerName, registrationRequest.replyTo)); + + trainerIDToActor.put(existingOrNewTrainerID, trainerActor); + + registrationRequest.replyTo.tell(new AkkamonNexus.TrainerRegistered( + existingOrNewTrainerID, + registrationRequest.session + )); + } + } else { + getContext() + .getLog() + .warn( + "Ignoring TrainerRegistration request for {}. This actor is responsible for {}.", + registrationRequest.sceneId, + this.sceneId); + } + return this; + } + +} diff --git a/domain/src/main/java/akkamon/domain/actors/Trainer.java b/domain/src/main/java/akkamon/domain/actors/Trainer.java new file mode 100644 index 0000000..4d4d9c5 --- /dev/null +++ b/domain/src/main/java/akkamon/domain/actors/Trainer.java @@ -0,0 +1,128 @@ +package akkamon.domain.actors; + +import akka.actor.typed.ActorRef; +import akka.actor.typed.Behavior; +import akka.actor.typed.javadsl.AbstractBehavior; +import akka.actor.typed.javadsl.ActorContext; +import akka.actor.typed.javadsl.Behaviors; +import akka.actor.typed.javadsl.Receive; +import akkamon.domain.Direction; +import akkamon.domain.TilePos; + +import java.util.LinkedList; +import java.util.Optional; +import java.util.Queue; + +import static akkamon.domain.actors.AkkamonNexus.*; + +public class Trainer extends AbstractBehavior { + + public interface Command { } + + public static class ReadMovementQueue implements Command { + final long requestId; + final ActorRef replyTo; + + public ReadMovementQueue(long requestId, ActorRef replyTo) { + this.requestId = requestId; + this.replyTo = replyTo; + } + } + + public static final class RespondMovementQueue { + final long requestId; + final TrainerID trainerID; + final Queue value; + + public RespondMovementQueue( + long requestId, + TrainerID trainerID, + Queue value + ) { + this.requestId = requestId; + this.trainerID = trainerID; + this.value = value; + } + } + + public static Behavior create(TrainerID trainerID) { + return Behaviors.setup(context -> new Trainer(context, trainerID)); + } + + private TrainerID trainerID; + + private Queue movementQueue = new LinkedList<>(); + + private Direction movementDirection = Direction.NONE; + + private Optional lastValidTilePos = Optional.empty(); + + public Trainer(ActorContext context, TrainerID trainerID) { + super(context); + this.trainerID = trainerID; + } + + @Override + public Receive createReceive() { + return newReceiveBuilder() + .onMessage( + ReadMovementQueue.class, + this::onReadMovementQueue + ) + .onMessage( + RequestTrainerOffline.class, + this::onTrainerOffline + ) + .onMessage( + RequestStartMoving.class, + this::onStartMoving + ) + .onMessage( + RequestStopMoving.class, + this::onStopMoving) + .onMessage( + RequestNewTilePos.class, + this::onNewTilePos + ) + .build(); + } + + private Behavior onTrainerOffline(RequestTrainerOffline trainerOfflineRequest) { + getContext().getLog().info("Trainer {} went offline, the actor has stopped! My supervisor should handle closing my connection!"); + return Behaviors.stopped(); + } + + private Trainer onReadMovementQueue(ReadMovementQueue readTrainerPositionRequest) { + readTrainerPositionRequest.replyTo.tell(new RespondMovementQueue( + readTrainerPositionRequest.requestId, + trainerID, + new LinkedList<>(movementQueue) + )); + this.movementQueue.clear(); + return this; + } + + private Trainer onNewTilePos(RequestNewTilePos newTilePosRequest) { + // getContext().getLog().info("Trainer {} has new {}.", trainerID, newTilePosRequest.tilePos); + if (isMoving()) { + this.movementQueue.add(this.movementDirection); + } + return this; + } + + private Trainer onStopMoving(RequestStopMoving stopMovingRequest) { + // getContext().getLog().info("Trainer {} stops to move {}.", trainerID, stopMovingRequest.direction); + this.movementDirection = Direction.NONE; + return this; + } + + private Trainer onStartMoving(RequestStartMoving startMovingRequest) { + // getContext().getLog().info("Trainer {} starts to move {}.", trainerID, startMovingRequest.direction); + this.movementDirection = startMovingRequest.direction; + return this; + } + + private boolean isMoving() { + return this.movementDirection != Direction.NONE; + } +} diff --git a/domain/src/test/java/akkamon/domain/AkkamonNexusTest.java b/domain/src/test/java/akkamon/domain/AkkamonNexusTest.java index d83bdcb..0ece974 100644 --- a/domain/src/test/java/akkamon/domain/AkkamonNexusTest.java +++ b/domain/src/test/java/akkamon/domain/AkkamonNexusTest.java @@ -3,6 +3,8 @@ package akkamon.domain; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.ActorRef; +import akkamon.domain.actors.AkkamonNexus; +import akkamon.domain.actors.SceneTrainerGroup; import org.junit.ClassRule; import org.junit.Test; -- cgit v1.2.3