From 5515478fe65cd513b74e25811b9b08f345f5eb41 Mon Sep 17 00:00:00 2001 From: Yannik Bretschneider Date: Wed, 19 May 2021 20:59:59 +0200 Subject: [PATCH] feat: implemented a pipelining system for the lobby to pipeline events through a number of segments for processing them --- .../server/Lobby/pipelining/PauseSegment.java | 99 ++++++++++++++++++- .../server/Lobby/pipelining/Pipeline.java | 58 +++++++++-- .../server/Lobby/pipelining/Segment.java | 22 ++++- .../Lobby/pipelining/PauseSegmentTest.java | 41 +++++++- 4 files changed, 208 insertions(+), 12 deletions(-) diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/Lobby/pipelining/PauseSegment.java b/Server/src/main/java/uulm/teamname/marvelous/server/Lobby/pipelining/PauseSegment.java index bf86d15..199a8ad 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/Lobby/pipelining/PauseSegment.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/Lobby/pipelining/PauseSegment.java @@ -1,2 +1,99 @@ -package uulm.teamname.marvelous.server.Lobby.pipelining;public class PauseSegment { +package uulm.teamname.marvelous.server.Lobby.pipelining; + +import uulm.teamname.marvelous.gamelibrary.Tuple; +import uulm.teamname.marvelous.gamelibrary.events.Event; +import uulm.teamname.marvelous.gamelibrary.events.EventBuilder; +import uulm.teamname.marvelous.gamelibrary.events.EventType; +import uulm.teamname.marvelous.gamelibrary.requests.Request; +import uulm.teamname.marvelous.gamelibrary.requests.RequestBuilder; +import uulm.teamname.marvelous.gamelibrary.requests.RequestType; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class PauseSegment implements Segment { + + private boolean paused; + + public PauseSegment(){ + paused = false; + } + + public void pauseGame(){ + if(!paused) + paused = true; + } + public void pauseEnd(){ + if(paused) + paused = false; + } + + public boolean isPaused() { + return paused; + } + + + /** + * Pipelining method to process a set of requests. + * The list of requests will be processed according to the following rules: + * + * @param packet + */ + @Override + public void processRequests(List packet, List carrier, AtomicBoolean abort) { + // check if there is a pause request (either start or stop) + if (packet.contains(new RequestBuilder(RequestType.PauseStartRequest).buildGameRequest())) { + if (!paused) { + // pause the game + pauseGame(); + // create a new PauseStartEvent + carrier.add(new EventBuilder(EventType.PauseStartEvent).buildGameEvent()); + } else { // if the game is already paused + abort.set(true); + return; + } + } else if (packet.contains(new RequestBuilder(RequestType.PauseStopRequest).buildGameRequest())) { + if (paused) { + // pause the game + pauseEnd(); + // create a new PauseStartRequest + carrier.add(new EventBuilder(EventType.PauseStopEvent).buildGameEvent()); + } else { // if the game is not paused + abort.set(true); + return; + } + } + + // filter the events + if (paused) { + for (Request request: packet) { + switch (request.type) { + case MeleeAttackRequest, + RangedAttackRequest, + MoveRequest, + ExchangeInfinityStoneRequest, + UseInfinityStoneRequest, + EndRoundRequest, + PauseStopRequest, + PauseStartRequest -> packet.remove(request); + + case DisconnectRequest, Req -> { /* do nothing */ } + } + } + } + } } diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/Lobby/pipelining/Pipeline.java b/Server/src/main/java/uulm/teamname/marvelous/server/Lobby/pipelining/Pipeline.java index 601d903..8f1cee2 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/Lobby/pipelining/Pipeline.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/Lobby/pipelining/Pipeline.java @@ -1,23 +1,65 @@ -package uulm.teamname.marvelous.server.Lobby; +package uulm.teamname.marvelous.server.Lobby.pipelining; +import uulm.teamname.marvelous.gamelibrary.Tuple; +import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.requests.Request; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +/** + * The {@link Pipeline} class pipelines {@link Request Requests} through {@link Segment Segments} to + * create {@link Event Events}. To add functionality without dabbling in the game library, create a new {@link Segment}. + */ public class Pipeline { - private final Segment[] segments; + /** The segments of the pipeline, which will be executed from left to right (index 0 to segments.size()) */ + private final ArrayList segments; - public Pipeline(Segment[] segments) { - this.segments = segments; + /** + * Creates a new empty {@link Pipeline} object + */ + public Pipeline() { + this.segments = new ArrayList<>(); } - public void processEvents(Request[] requests) { - Request[] out; + /** + * Pipeline the {@link Request Requests} through all {@link Segment Segments} that are in the pipeline. + * The {@link Request Requests} are declared as the Packet. The {@link Request Requests} are filtered + * by each {@link Segment segment}, whereby each {@link Segment segment} takes (and thereby removes) all + * {@link Event events} relevant to the {@link Segment segment}. The {@link Event Events} are returned at the + * end of the pipeline. + * @param requests are the requests that are being pipelined through the pipeline + * @return a {@link Tuple}<{@link Boolean}, {@link Event}[]>, whereby the {@link Boolean} declares whether the + * execution of the {@link Pipeline} is successful. If that is the case, the events can be ignored, + * and an error message can be sent to the client. + */ + public Tuple processRequests(Request[] requests) { + List packet = Arrays.asList(requests); + List carrier = new ArrayList<>(); + AtomicBoolean abort = new AtomicBoolean(); + abort.set(false); for (Segment segment: segments) { - out = segment.processEvents(requests); + segment.processRequests(packet, carrier, abort); + if (packet.size() == 0 || abort.get()) { + break; + } } + return new Tuple (abort.get(), carrier.toArray(new Event[0])); } - + /** + * Adds a segment to the pipeline. Note that ORDER MATTERS! The first segment added will be the + * first segment to process events. Also note that segments cannot be removed once added. This is by + * design, as removing segments would give the pipeline runtime customizability, which it is not supposed to + * possess in the first place. + * @param toAdd is the segment that will be added to the pipeline + * @return the pipeline itself for chaining + */ + public Pipeline addSegment(Segment toAdd) { + this.segments.add(toAdd); + return this; + } } diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/Lobby/pipelining/Segment.java b/Server/src/main/java/uulm/teamname/marvelous/server/Lobby/pipelining/Segment.java index 48d9ad2..9ca634c 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/Lobby/pipelining/Segment.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/Lobby/pipelining/Segment.java @@ -1,8 +1,26 @@ -package uulm.teamname.marvelous.server.Lobby; +package uulm.teamname.marvelous.server.Lobby.pipelining; +import uulm.teamname.marvelous.gamelibrary.Tuple; import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.requests.Request; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + public interface Segment { - public Request[] processEvents(Request[] events); + /** + * Pipelining method to process a set of requests. + * The list of requests will be processed according to some set of rules. Hereby, + * the {@link Request Requests} in the packet will be filtered out as appropriate, + * whereby the resulting {@link Event Events} are appended to the carrier. + * @param packet is a {@link List} of {@link Request Requests} that is filtered + * by the {@link Segment} as appropriate + * @param carrier is a {@link List} of {@link Event Events} that is appended to + * if new requests are generated from the execution of the {@link Segment} + * @param abort is an {@link AtomicBoolean} describing whether an error has occurred during the execution of + * the {@link Segment} Note that error here does not describe an execution error + * of the segment, but instead an error in the events passed to it, like for example moving into a Rock. + * The conventional way of setting this boolean is to write {@code abort.set(true); return;} + */ + public void processRequests(List packet, List carrier, AtomicBoolean abort); } diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/Lobby/pipelining/PauseSegmentTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/Lobby/pipelining/PauseSegmentTest.java index 7a0e1e7..66b5826 100644 --- a/Server/src/test/java/uulm/teamname/marvelous/server/Lobby/pipelining/PauseSegmentTest.java +++ b/Server/src/test/java/uulm/teamname/marvelous/server/Lobby/pipelining/PauseSegmentTest.java @@ -1,5 +1,44 @@ +package uulm.teamname.marvelous.server.Lobby.pipelining; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.*; + import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; + class PauseSegmentTest { - + + PauseSegment pauseSegment; + + @BeforeEach + void setUp() { + pauseSegment = new PauseSegment(); + } + + @Test + void pauseGame() { + assertThat(pauseSegment.isPaused()).isFalse(); + pauseSegment.pauseGame(); + assertThat(pauseSegment.isPaused()).isTrue(); + pauseSegment.pauseGame(); + assertThat(pauseSegment.isPaused()).isTrue(); + } + + @Test + void pauseEnd() { + assertThat(pauseSegment.isPaused()).isFalse(); + pauseSegment.pauseEnd(); + assertThat(pauseSegment.isPaused()).isFalse(); + pauseSegment.pauseGame(); + assertThat(pauseSegment.isPaused()).isTrue(); + pauseSegment.pauseEnd(); + assertThat(pauseSegment.isPaused()).isFalse(); + } + + @Test + void processEvents() { + // TODO: check that events get pipelined normally if not paused, but filtered if paused + // and check that events work properly + } } \ No newline at end of file