feat: implemented a pipelining system for the lobby to pipeline events through a number of segments for processing them

This commit is contained in:
Yannik Bretschneider 2021-05-19 20:59:59 +02:00
parent d2d9712b61
commit 5515478fe6
4 changed files with 208 additions and 12 deletions

View File

@ -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:
* <ul>
* <li>If there is a PauseStartRequest, the paused value will be set to true</li>
* <li>Any CharacterRequests will be removed from the requests if the game is paused. These include:
* <ul>
* <li>MeeleAttackRequest</li>
* <li>RangedAttackRequest</li>
* <li>MoveRequest</li>
* <li>ExchangeInfinityStoneRequest</li>
* <li>UseInfinityStoneRequest</li>
* <li>EndRoundRequest</li>
* </ul>
* </li>
* <li>If a mistake is made (request is sent while pause active), the client will get an error</li>
* <li>If there is a PauseStopRequest the game will be unpaused</li>
* </ul>
* @param packet
*/
@Override
public void processRequests(List<Request> packet, List<Event> 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 */ }
}
}
}
}
} }

View File

@ -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 uulm.teamname.marvelous.gamelibrary.requests.Request;
import java.util.ArrayList; 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 { 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<Segment> 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 <b>Packet</b>. 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. <b>If that is the case, the events can be ignored,
* and an error message can be sent to the client</b>.
*/
public Tuple<Boolean, Event[]> processRequests(Request[] requests) {
List<Request> packet = Arrays.asList(requests);
List<Event> carrier = new ArrayList<>();
AtomicBoolean abort = new AtomicBoolean();
abort.set(false);
for (Segment segment: segments) { for (Segment segment: segments) {
out = segment.processEvents(requests); segment.processRequests(packet, carrier, abort);
if (packet.size() == 0 || abort.get()) {
break;
}
} }
return new Tuple<Boolean, Event[]> (abort.get(), carrier.toArray(new Event[0]));
} }
/**
* Adds a segment to the pipeline. Note that <b>ORDER MATTERS!</b> The first segment added will be the
* first segment to process events. Also note that segments <b>cannot be removed once added</b>. 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;
}
} }

View File

@ -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.events.Event;
import uulm.teamname.marvelous.gamelibrary.requests.Request; import uulm.teamname.marvelous.gamelibrary.requests.Request;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public interface Segment { 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 <b>error</b> here does <b>not describe an execution error</b>
* 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<Request> packet, List<Event> carrier, AtomicBoolean abort);
} }

View File

@ -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.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class PauseSegmentTest { 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
}
} }