Skip to content

Commit a792991

Browse files
Merge branch 'fix-event-queue-interrupt'
2 parents 3f02332 + 1b73093 commit a792991

File tree

246 files changed

+68279
-144
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

246 files changed

+68279
-144
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ bin/
1919
obj/
2020
generated/
2121

22+
/bot-api/dotnet/docs/ReadMe.md
23+
/bot-api/dotnet/docs/Release-notes.txt
24+
2225
*.iml
2326

2427
/games.properties

VERSIONS.MD

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
## 📦 0.24.5
1+
## 📦 0.25.0 - Fix for Ranks and Event Queue improvements - 11-Oct-2024
22

33
#### 🐞 Bug Fixes
44

55
- Server:
66
- #108: The ranks were wrong as they did not reflect the total score and placements
7+
- Bot API:
8+
- An interruptible event was not interrupted when `setInterruptible(true)` was called. The event queue contained
9+
more bot events than it should, due to this bug.
10+
- The event queue and dispatcher was improved so it is closer to the implementation for the original Robocode.
711

8-
## 📦 0.24.4 - Support for IPv6 endpoints
12+
## 📦 0.24.4 - Support for IPv6 endpoints - 19-Sep-2024
913

1014
#### Improvements
1115

bot-api/dotnet/Robocode.TankRoyale.BotApi/src/events/BotEvent.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,7 @@
55
/// </summary>
66
public abstract class BotEvent : IEvent
77
{
8-
/// <summary>Turn number when the event occurred.</summary>
98
public int TurnNumber { get; }
10-
11-
/// <summary>
12-
/// Indicates if this event is critical, and hence should not be removed from event queue when it gets old.
13-
/// </summary>
14-
/// <return>
15-
/// <c>true</c> if this event is critical; <c>false</c> otherwise. Default is <c>false</c>.
16-
/// </return>
179
public virtual bool IsCritical => false;
18-
19-
///<summary>
20-
/// Initializes a new instance of the Event class.
21-
///</summary>
22-
///<param name="turnNumber">Is the turn number when the event occurred.</param>
2310
protected BotEvent(int turnNumber) => TurnNumber = turnNumber;
2411
}

bot-api/dotnet/Robocode.TankRoyale.BotApi/src/internal/BaseBotInternals.cs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public sealed class BaseBotInternals
3737
private GameSetup gameSetup;
3838

3939
private InitialPosition initialPosition;
40-
40+
4141
private E.TickEvent tickEvent;
4242
private long? ticksStart;
4343

@@ -71,6 +71,8 @@ public sealed class BaseBotInternals
7171

7272
private ICollection<int> teammateIds;
7373

74+
private int lastExecuteTurnNumber;
75+
7476
internal BaseBotInternals(IBaseBot baseBot, BotInfo botInfo, Uri serverUrl, string serverSecret)
7577
{
7678
this.baseBot = baseBot;
@@ -103,7 +105,7 @@ private void RedirectStdOutAndStdErr()
103105
{
104106
recordingStdOut = new RecordingTextWriter(Console.Out);
105107
recordingStdErr = new RecordingTextWriter(Console.Error);
106-
108+
107109
Console.SetOut(recordingStdOut);
108110
Console.SetError(recordingStdErr);
109111
}
@@ -166,12 +168,12 @@ public void EnableEventHandling(bool enable)
166168
eventHandlingDisabledTurn = enable ? 0 : CurrentTickOrThrow.TurnNumber;
167169
}
168170

169-
public bool IsEventHandlingDisabled()
171+
private bool IsEventHandlingDisabled()
170172
{
171173
// Important! Allow an additional turn so events like RoundStarted can be handled
172174
return eventHandlingDisabledTurn != 0 && eventHandlingDisabledTurn < (CurrentTickOrThrow.TurnNumber - 1);
173175
}
174-
176+
175177
public void SetStopResumeHandler(IStopResumeListener listener) => stopResumeListener = listener;
176178

177179
private static S.BotIntent NewBotIntent() => new()
@@ -208,6 +210,7 @@ private void OnRoundStarted(E.RoundStartedEvent e)
208210
eventQueue.Clear();
209211
IsStopped = false;
210212
eventHandlingDisabledTurn = 0;
213+
lastExecuteTurnNumber = -1;
211214
}
212215

213216
private void OnNextTurn(E.TickEvent e)
@@ -258,6 +261,12 @@ internal void Execute()
258261
return;
259262

260263
var turnNumber = CurrentTickOrThrow.TurnNumber;
264+
if (turnNumber == lastExecuteTurnNumber)
265+
{
266+
return; // skip this execute, as we have already run this method within the same turn
267+
}
268+
269+
lastExecuteTurnNumber = turnNumber;
261270

262271
DispatchEvents(turnNumber);
263272
SendIntent();
@@ -278,6 +287,7 @@ private void TransferStdOutToBotIntent()
278287
var output = recordingStdOut.ReadNext();
279288
BotIntent.StdOut = output.Length > 0 ? output : null;
280289
}
290+
281291
if (recordingStdErr != null)
282292
{
283293
var error = recordingStdErr.ReadNext();
@@ -312,6 +322,11 @@ private void DispatchEvents(int turnNumber)
312322
}
313323
catch (Exception e)
314324
{
325+
if (e is InterruptEventHandlerException)
326+
{
327+
return;
328+
}
329+
315330
Console.Error.WriteLine(e);
316331
}
317332
}
@@ -328,8 +343,6 @@ private void DispatchEvents(int turnNumber)
328343

329344
internal E.TickEvent CurrentTickOrThrow => tickEvent ?? throw new BotException(TickNotAvailableMsg);
330345

331-
internal E.TickEvent CurrentTickOrNull => tickEvent;
332-
333346
private long TicksStart
334347
{
335348
get
@@ -638,12 +651,12 @@ internal void SendTeamMessage(int? teammateId, object message)
638651

639652
internal int GetPriority(Type eventType)
640653
{
641-
if (!eventPriorities.ContainsKey(eventType))
654+
if (!eventPriorities.TryGetValue(eventType, out var priority))
642655
{
643656
throw new InvalidOperationException($"Could not get event priority for the type: {eventType.Name}");
644657
}
645658

646-
return eventPriorities[eventType];
659+
return priority;
647660
}
648661

649662
internal void SetPriority(Type eventType, int priority) => eventPriorities[eventType] = priority;

bot-api/dotnet/Robocode.TankRoyale.BotApi/src/internal/EventQueue.cs

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ internal void Clear()
3232
{
3333
events = events.Clear();
3434
baseBotInternals.Conditions.Clear(); // conditions might be added in the bots Run() method each round
35-
currentTopEvent = null;
3635
currentTopEventPriority = MinValue;
3736
}
3837

@@ -71,25 +70,46 @@ internal void AddEventsFromTick(TickEvent tickEvent)
7170

7271
internal void DispatchEvents(int turnNumber)
7372
{
73+
// DumpEvents(); // for debugging purposes
74+
7475
RemoveOldEvents(turnNumber);
7576
SortEvents();
7677

7778
while (IsBotRunning)
7879
{
79-
var botEvent = GetNextEvent();
80-
if (botEvent == null || IsSameEventAndInterruptible(botEvent))
80+
BotEvent currentEvent = GetNextEvent();
81+
if (currentEvent == null) {
82+
break;
83+
}
84+
if (IsSameEvent(currentEvent))
85+
{
86+
if (IsInterruptible)
87+
{
88+
SetInterruptible(currentEvent.GetType(), false); // clear interruptible flag
89+
90+
// We are already in an event handler, took action, and a new event was generated.
91+
// So we want to break out of the old handler to process the new event here.
92+
throw new InterruptEventHandlerException();
93+
}
8194
break;
95+
}
8296

83-
var priority = GetPriority(botEvent);
84-
var originalTopEventPriority = currentTopEventPriority;
97+
int oldTopEventPriority = currentTopEventPriority;
8598

86-
currentTopEventPriority = priority;
87-
currentTopEvent = botEvent;
99+
currentTopEventPriority = GetPriority(currentEvent);
100+
currentTopEvent = currentEvent;
88101

89-
try {
90-
HandleEvent(botEvent, turnNumber);
91-
} finally {
92-
currentTopEventPriority = originalTopEventPriority;
102+
try
103+
{
104+
HandleEvent(currentEvent, turnNumber);
105+
}
106+
catch (InterruptEventHandlerException)
107+
{
108+
// Expected when event handler is interrupted on purpose
109+
}
110+
finally
111+
{
112+
currentTopEventPriority = oldTopEventPriority;
93113
}
94114
}
95115
}
@@ -117,19 +137,27 @@ private BotEvent GetNextEvent()
117137
return botEvent;
118138
}
119139

120-
private bool IsSameEventAndInterruptible(BotEvent botEvent) =>
121-
GetPriority(botEvent) == currentTopEventPriority && (currentTopEventPriority > MinValue && IsInterruptible);
140+
private bool IsSameEvent(BotEvent botEvent) =>
141+
GetPriority(botEvent) == currentTopEventPriority;
122142

123143
private int GetPriority(BotEvent botEvent)
124144
{
125145
return baseBotInternals.GetPriority(botEvent.GetType());
126146
}
127147

128-
private void HandleEvent(BotEvent botEvent, int turnNumber) {
129-
if (IsNotOldOrIsCriticalEvent(botEvent, turnNumber)) {
130-
botEventHandlers.Fire(botEvent);
148+
private void HandleEvent(BotEvent botEvent, int turnNumber)
149+
{
150+
try
151+
{
152+
if (IsNotOldOrIsCriticalEvent(botEvent, turnNumber))
153+
{
154+
botEventHandlers.Fire(botEvent);
155+
}
156+
}
157+
finally
158+
{
159+
SetInterruptible(botEvent.GetType(), false); // clear interruptible flag
131160
}
132-
SetInterruptible(botEvent.GetType(), false);
133161
}
134162

135163
public int Compare(BotEvent botEvent1, BotEvent botEvent2)
@@ -154,27 +182,25 @@ public int Compare(BotEvent botEvent1, BotEvent botEvent2)
154182

155183
private static bool IsNotOldOrIsCriticalEvent(BotEvent botEvent, int turnNumber)
156184
{
157-
var isNotOld = botEvent.TurnNumber + MaxEventAge >= turnNumber;
158-
var isCritical = botEvent.IsCritical;
159-
return isNotOld || isCritical;
185+
var isNotOld = botEvent.TurnNumber >= turnNumber - MaxEventAge;
186+
return isNotOld || botEvent.IsCritical;
160187
}
161188

162189
private static bool IsOldAndNonCriticalEvent(BotEvent botEvent, int turnNumber)
163190
{
164-
var isOld = botEvent.TurnNumber + MaxEventAge < turnNumber;
165-
var isNonCritical = !botEvent.IsCritical;
166-
return isOld && isNonCritical;
191+
var isOld = botEvent.TurnNumber < turnNumber - MaxEventAge;
192+
return isOld && !botEvent.IsCritical;
167193
}
168194

169195
private void AddEvent(BotEvent botEvent)
170196
{
171-
if (events.Count > MaxQueueSize)
197+
if (events.Count <= MaxQueueSize)
172198
{
173-
Console.Error.WriteLine("Maximum event queue size has been reached: " + MaxQueueSize);
199+
events = events.Add(botEvent);
174200
}
175201
else
176202
{
177-
events = events.Add(botEvent);
203+
Console.Error.WriteLine("Maximum event queue size has been reached: " + MaxQueueSize);
178204
}
179205
}
180206

@@ -185,4 +211,10 @@ private void AddCustomEvents()
185211
AddEvent(new CustomEvent(baseBotInternals.CurrentTickOrThrow.TurnNumber, condition));
186212
}
187213
}
214+
215+
private void DumpEvents()
216+
{
217+
string eventsString = string.Join(", ", events.Select(e => $"{e.GetType().Name}({e.TurnNumber})"));
218+
Console.WriteLine($"events: {eventsString}");
219+
}
188220
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Robocode.TankRoyale.BotApi.Internal;
2+
3+
// Exception used for interrupting event handlers.
4+
public class InterruptEventHandlerException : System.Exception
5+
{
6+
}

bot-api/dotnet/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,15 @@ tasks {
8383
}
8484

8585
register("pushLocal") {
86-
dependsOn(build)
86+
dependsOn(build, prepareNugetDocs)
8787

8888
doLast {
8989
val userhome = System.getenv("USERPROFILE") ?: System.getenv("HOME")
9090
println("$userhome/.nuget/packages/${artifactName.toLowerCaseAsciiOnly()}/$version")
9191
delete("$userhome/.nuget/packages/${artifactName.toLowerCaseAsciiOnly()}/$version")
9292
exec {
9393
workingDir("Robocode.TankRoyale.BotApi/bin/Release")
94-
commandLine("dotnet", "nuget", "push", "$artifactName.$version.nupkg", "-s", "local")
94+
commandLine("dotnet", "nuget", "push", "$artifactName.$version.nupkg", "--source", "$userhome/.nuget/packages")
9595
}
9696
}
9797
}

bot-api/java/src/main/java/dev/robocode/tankroyale/botapi/internal/BaseBotInternals.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ public final class BaseBotInternals {
113113

114114
private final Map<Class<? extends BotEvent>, Integer> eventPriorities = initializeEventPriorities();
115115

116+
private int lastExecuteTurnNumber;
117+
116118
public BaseBotInternals(IBaseBot baseBot, BotInfo botInfo, URI serverUrl, String serverSecret) {
117119
this.baseBot = baseBot;
118120
this.botInfo = (botInfo == null) ? EnvVars.getBotInfo() : botInfo;
@@ -252,6 +254,7 @@ private void onRoundStarted(RoundStartedEvent e) {
252254
eventQueue.clear();
253255
isStopped = false;
254256
eventHandlingDisabledTurn = 0;
257+
lastExecuteTurnNumber = -1;
255258
}
256259

257260
private void onNextTurn(TickEvent e) {
@@ -299,6 +302,10 @@ public void execute() {
299302
return;
300303

301304
final var turnNumber = getCurrentTickOrThrow().getTurnNumber();
305+
if (turnNumber == lastExecuteTurnNumber) {
306+
return; // skip this execute, as we have already run this method within the same turn
307+
}
308+
lastExecuteTurnNumber = turnNumber;
302309

303310
dispatchEvents(turnNumber);
304311
sendIntent();

0 commit comments

Comments
 (0)