Skip to content

Commit 2fe6046

Browse files
committed
New module and SSE
1 parent b2cb801 commit 2fe6046

18 files changed

Lines changed: 488 additions & 44 deletions

File tree

heroes/demo/index.html

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Event Transport Demo</title>
6+
<style>
7+
body { font-family: sans-serif; margin: 20px; }
8+
#log { border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: scroll; background: #f9f9f9; }
9+
.event { margin-bottom: 5px; border-bottom: 1px solid #eee; }
10+
.controls { margin-bottom: 10px; }
11+
</style>
12+
</head>
13+
<body>
14+
<h1>Event Transport Demo</h1>
15+
<div class="controls">
16+
<label for="mode">Transport Mode:</label>
17+
<select id="mode">
18+
<option value="LP">Long Polling</option>
19+
<option value="SSE">SSE</option>
20+
</select>
21+
<button id="start">Start Listening</button>
22+
<button id="stop">Stop</button>
23+
<input type="text" id="msg" placeholder="Message to send">
24+
<button id="send">Send Event</button>
25+
</div>
26+
<div id="log"></div>
27+
28+
<script>
29+
const logDiv = document.getElementById('log');
30+
const modeSelect = document.getElementById('mode');
31+
const startBtn = document.getElementById('start');
32+
const stopBtn = document.getElementById('stop');
33+
const sendBtn = document.getElementById('send');
34+
const msgInput = document.getElementById('msg');
35+
36+
let lastOffset = 0;
37+
let running = false;
38+
let eventSource = null;
39+
40+
function log(msg) {
41+
const div = document.createElement('div');
42+
div.className = 'event';
43+
div.textContent = `${new Date().toLocaleTimeString()}: ${msg}`;
44+
logDiv.appendChild(div);
45+
logDiv.scrollTop = logDiv.scrollHeight;
46+
}
47+
48+
async function startLongPolling() {
49+
running = true;
50+
log('Starting Long Polling...');
51+
while (running) {
52+
try {
53+
const response = await fetch(`http://localhost:8081/events?lastOffset=${lastOffset}&timeout=10`);
54+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
55+
const events = await response.json();
56+
events.forEach(event => {
57+
log(`[LP] Received: ${JSON.stringify(event.data.message)} (ID: ${event.id})`);
58+
lastOffset = Math.max(lastOffset, event.id);
59+
});
60+
} catch (e) {
61+
if (running) {
62+
log(`Error: ${e.message}`);
63+
await new Promise(r => setTimeout(r, 2000));
64+
}
65+
}
66+
}
67+
}
68+
69+
function startSse() {
70+
log('Starting SSE...');
71+
running = true;
72+
eventSource = new EventSource(`http://localhost:8081/events?lastOffset=${lastOffset}`);
73+
eventSource.onmessage = (event) => {
74+
const data = JSON.parse(event.data);
75+
log(`[SSE] Received: ${JSON.stringify(data.data.message)} (ID: ${data.id})`);
76+
lastOffset = Math.max(lastOffset, data.id);
77+
};
78+
eventSource.onerror = (e) => {
79+
log('SSE Error, reconnecting...');
80+
};
81+
}
82+
83+
startBtn.onclick = () => {
84+
if (running) return;
85+
const mode = modeSelect.value;
86+
if (mode === 'LP') {
87+
startLongPolling();
88+
} else {
89+
startSse();
90+
}
91+
};
92+
93+
stopBtn.onclick = () => {
94+
running = false;
95+
if (eventSource) {
96+
eventSource.close();
97+
eventSource = null;
98+
}
99+
log('Stopped.');
100+
};
101+
102+
sendBtn.onclick = async () => {
103+
const message = msgInput.value || 'Hello';
104+
try {
105+
const response = await fetch('http://localhost:8081/events', {
106+
method: 'POST',
107+
headers: { 'Content-Type': 'application/json' },
108+
body: JSON.stringify({ message: message })
109+
});
110+
if (response.ok) {
111+
log(`Sent: ${message}`);
112+
msgInput.value = '';
113+
}
114+
} catch (e) {
115+
log(`Send Error: ${e.message}`);
116+
}
117+
};
118+
</script>
119+
</body>
120+
</html>

heroes/pom.xml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>ru.mifi.practice</groupId>
7+
<artifactId>AaDS</artifactId>
8+
<version>${revision}</version>
9+
<relativePath>../pom.xml</relativePath>
10+
</parent>
11+
<artifactId>heroes</artifactId>
12+
<name>heroes</name>
13+
<description>Модуль heroes с поддержкой LongPolling и SSE.</description>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>com.google.guava</groupId>
18+
<artifactId>guava</artifactId>
19+
</dependency>
20+
<dependency>
21+
<groupId>io.projectreactor</groupId>
22+
<artifactId>reactor-core</artifactId>
23+
<version>3.7.12</version>
24+
</dependency>
25+
<dependency>
26+
<groupId>com.fasterxml.jackson.core</groupId>
27+
<artifactId>jackson-databind</artifactId>
28+
<version>2.17.2</version>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.slf4j</groupId>
32+
<artifactId>slf4j-api</artifactId>
33+
<version>2.0.16</version>
34+
</dependency>
35+
<dependency>
36+
<groupId>ch.qos.logback</groupId>
37+
<artifactId>logback-classic</artifactId>
38+
<version>1.5.16</version>
39+
</dependency>
40+
<dependency>
41+
<groupId>io.projectreactor</groupId>
42+
<artifactId>reactor-test</artifactId>
43+
<version>3.7.9</version>
44+
<scope>test</scope>
45+
</dependency>
46+
</dependencies>
47+
</project>

vol_/src/main/java/ru/mifi/practice/voln/heroes/BattleMap.java renamed to heroes/src/main/java/ru/mifi/practice/voln/heroes/BattleMap.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,10 +342,11 @@ private void removeStack(int row, int column) {
342342
left.remove(id);
343343
right.remove(id);
344344
map[row][column] = null;
345+
turnQueue.remove(id);
345346
}
346347

347348
private void checkTurnEnd() {
348-
if (turnQueue.isEmpty()) {
349+
while (turnQueue.isEmpty()) {
349350
leftTurn = !leftTurn;
350351
String msg = String.format("--- Ход %s ---", leftTurn ? "ЛЕВЫХ" : "ПРАВЫХ");
351352
support.firePropertyChange("log", null, msg);
@@ -358,8 +359,8 @@ private void checkTurnEnd() {
358359
sk.stack.setCounterAttacked(false);
359360
});
360361
fillTurnQueue();
361-
if (turnQueue.isEmpty()) {
362-
checkTurnEnd(); // Switch back if other side is also empty (should not happen in normal game)
362+
if (left.isEmpty() && right.isEmpty()) {
363+
break;
363364
}
364365
}
365366
}

vol_/src/main/java/ru/mifi/practice/voln/heroes/Constants.java renamed to heroes/src/main/java/ru/mifi/practice/voln/heroes/Constants.java

File renamed without changes.

vol_/src/main/java/ru/mifi/practice/voln/heroes/Main.java renamed to heroes/src/main/java/ru/mifi/practice/voln/heroes/Main.java

File renamed without changes.

vol_/src/main/java/ru/mifi/practice/voln/heroes/Unit.java renamed to heroes/src/main/java/ru/mifi/practice/voln/heroes/Unit.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import lombok.Setter;
55

66
import java.util.Comparator;
7-
import java.util.Iterator;
87
import java.util.PriorityQueue;
98
import java.util.Queue;
109
import java.util.concurrent.ThreadLocalRandom;
@@ -116,18 +115,18 @@ public boolean isEmpty() {
116115

117116
public void damage(int attackAmount) {
118117
int remaining = attackAmount;
119-
Iterator<Unit> it = units.iterator();
120-
while (it.hasNext() && remaining > 0) {
121-
Unit unit = it.next();
118+
while (!units.isEmpty() && remaining > 0) {
119+
Unit unit = units.poll();
122120
int kick = Math.max(0, remaining - unit.defense());
123-
if (kick == 0) {
121+
if (kick <= 0) {
122+
units.add(unit);
124123
break;
125124
}
126-
if (unit.health() - kick <= 0) {
127-
it.remove();
128-
remaining -= kick - unit.health();
125+
if (unit.health() <= kick) {
126+
remaining = kick - unit.health();
129127
} else {
130128
unit.takeDamage(kick);
129+
units.add(unit);
131130
remaining = 0;
132131
}
133132
}

vol_/src/main/java/ru/mifi/practice/voln/heroes/ui/BattleGui.java renamed to heroes/src/main/java/ru/mifi/practice/voln/heroes/ui/BattleGui.java

File renamed without changes.

vol_/src/main/java/ru/mifi/practice/voln/heroes/ui/BattlePanel.java renamed to heroes/src/main/java/ru/mifi/practice/voln/heroes/ui/BattlePanel.java

File renamed without changes.

vol_/src/main/java/ru/mifi/practice/voln/polling/Event.java renamed to heroes/src/main/java/ru/mifi/practice/voln/polling/Event.java

File renamed without changes.

vol_/src/main/java/ru/mifi/practice/voln/polling/EventService.java renamed to heroes/src/main/java/ru/mifi/practice/voln/polling/EventService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public interface EventService {
1414

1515
Flux<Event> getEvents(Long lastOffset, long timeoutSeconds);
1616

17+
Flux<Event> getEventStream(Long lastOffset);
18+
1719
final class Default implements EventService {
1820
private final ConcurrentNavigableMap<Long, Event> eventBuffer = new ConcurrentSkipListMap<>();
1921
private final Sinks.Many<Event> eventSink = Sinks.many().multicast().onBackpressureBuffer(1000);
@@ -47,6 +49,13 @@ public Flux<Event> getEvents(Long lastOffset, long timeoutSeconds) {
4749
.timeout(Duration.ofSeconds(timeoutSeconds), Mono.empty())));
4850
}
4951

52+
@Override
53+
public Flux<Event> getEventStream(Long lastOffset) {
54+
long offset = lastOffset != null ? lastOffset : 0L;
55+
return getHistoricalEvents(offset)
56+
.concatWith(eventSink.asFlux().filter(event -> event.id() > offset));
57+
}
58+
5059
private Flux<Event> getHistoricalEvents(long lastOffset) {
5160
return Flux.fromIterable(eventBuffer.tailMap(lastOffset + 1).values());
5261
}

0 commit comments

Comments
 (0)