-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement graphical debugging via SVG #116
Implement graphical debugging via SVG #116
Conversation
Thank you for the nice PR, and you have done some really impressive work. 👍😊 I have been a bit reluctant about using SVG instead of basic primitive 2D objects, but I think you got a good point. I agree that most platforms have good support for SVG. We should be able to support it in the UI and the current Bot API for Java and C#. And the debug painting is optional, so if some platform or language does not support it, then the Bot API for that platform or language might not be able to support SVG easily. The worst-case scenario is to let the bots output SVG directly as a plain SVG string. So, I am not worried about this part. Your solution with using the drawing context ( Regarding the items to be tackled, I will answer those in the following. Enable/disable graphical debuggingYes, we need a switch to enable/disable graphical debugging.
Extending the API to include .NET/C# APIYes, every new feature (and bugfix) needs to be applied to every Bot API. Luckily, we only have 2 at the moment. I guess we can use Coordinate TransformI am happy that you bring up this topic and not just ignore it. 👍 I ran into the exact same issue when implementing the graphical debugging feature for the original Robocode. MirroredGraphics is used something like this: mirroredGraphics.bind(g, battleField.getHeight()); // we need the view height for the mirror transformation
// ...
// Use mirroredGraphics for painting graphics e.g. with paint(Graphics2D g)
// ...
mirroredGraphics.release(); // restores the original graphics state of the input Graphics2D object The drawRobotPaint() from the original Robocode is a good example of how it is being used. Also note that another class, GraphicsState, is used for saving and restoring the current state of the original Graphics2D instance. Sample bot (TO-DO)When the feature has been implemented, we should also include the feature in some of the sample bots. In fact, I did not include the I will checkout the code and try out your work to see if we need to adjust something and get a fell of the changes as well. 🙂 |
Thank you for the extensive reply! If you would rather not go with SVG for debugging, there is no expectation from me that you do simply because I invested some time in this prototype. My motivation was just to validate my approach. I would be curious to hear your concerns, however. I would make the argument that SVG is nothing but a well-established format of graphical primitives.
For Python, there is drawsvg, which is indeed a very good API. For the web you could use any of the well-established drawing libraries.
This was one of my primary motivations for going this route! Enabling/DisablingI fully agree that it needs to be possible to enable/disable graphical debugging per bot. However, there are different ways to make that happen. The one you're proposing sounds like the best, but also most complicated solution. I imagine bots would have an extra flag on the server defining if that particular bot is allowed to send debug information. It can be changed by any controller and the server forwards debug information only for bots that have this flag. Bots are also informed if they are currently permitted to send debug information. I would also like to make this flag configurable via environment variables, the command line or something else so you don't always need to click the button while developing a bot. Drawing in .NETYes, I believe picking a native API makes more sense than porting Graphics2D to .NET. However, I'm not sure that Svg.Skia, etc. are a good fit. I have no prior experience with them, but it seems to me they all mainly support rendering an SVG to screen. This would be useful for writing a GUI in C#, but not so much for the Bot API. I currently don't see anything in them to create SVGs. I'm not currently sure what the best library to use would be, but as a fallback we could always implement our own, supporting the same primitives you would have otherwise supported explicitly in the API. EDIT: VectSharp looks fairly nice and has a similar API to Graphics2D. Coordinate TransformInteresting! I was hoping you had something to share from the original Robocode. I'll take a look at In my last bot, I did draw text extensively for debugging and the coordinate transform was fully tranparent, which I suppose is the ideal. Sample BotI did add some arbitrary drawing commands to Crazy and verified they worked as I expected. However, because they were not sensible, I didn't add them to the PR. I expect the existing draw commands could be ported 1:1 for the sample bots. |
First of all. I checked out your code for Graphical Debugging, and I got it up and running, and did some simple painting from a bot. 👍🙂 The reason why I have been reluctant on using SVG is mainly due to:
But in the end this might not be a problem if the XML is fast to parse, and lengthy SVG is not a problem when transmitted over the network. Currently JSVG outputs this SVG for a red filled rectangle: <!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.0//EN'
'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd'>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" style="fill-opacity:1; color-rendering:auto; color-interpolation:auto; text-rendering:auto; stroke:black; stroke-linecap:square; stroke-miterlimit:10; shape-rendering:auto; stroke-opacity:1; fill:black; stroke-dasharray:none; font-weight:normal; stroke-width:1; font-family:'Dialog'; font-style:normal; stroke-linejoin:miter; font-size:12px; stroke-dashoffset:0; image-rendering:auto;" width="800" height="600" xmlns="http://www.w3.org/2000/svg"
><!--Generated by the Batik Graphics2D SVG Generator--><defs id="genericDefs"
/><g
><g style="fill:rgb(255,0,0); fill-opacity:0.498; stroke-opacity:0.498; stroke:rgb(255,0,0);"
><rect x="10" width="50" height="50" y="10" style="stroke:none;"
/></g
></g
></svg
> But we might optimize it to this: <svg width="800" height="600" xmlns="http://www.w3.org/2000/svg">
<g><rect x="10" y="10" width="50" height="50" fill="rgba(255,0,0,0.498)"/></g>
</svg> But this is an optimization issue, and I don't think we need to handle this now. 😉 |
Great to hear it's working for you as well. I appreciate your reply and your reasoning. I don't think any of these points are a showstopper, though. SVG is much more powerful than the graphical debugging primitives need to be, but I very much enjoyed making graphical debugging output pretty, so this extra power may also be a benefit. And we don't need to implement SVG ourselves. Regarding performance, there is probably a small penalty compared to a simpler list of primitives directly in JSON, but I expect this to be negligible. I don't expect to need graphical debugging at tick rates higher than, say, 100 TPS since you wouldn't make out much at that speed anyway. Since SVG compresses extremely well, we're talking about a fex extra kbps here, which would barely have an impact on any modern connection. If we're communicating locally only (which is the likely scenario for debugging), data transfer is virtually instant since most OSes optimize local connections to bypass the majority of the network stack. As for parsing and drawing, I have yet to conduct an experiment with complex graphics, but I don't believe we're going to hit a bottle neck. As for the SVG generated by Batik, yes, it is very verbose and can certainly be optimized, but I'd look at that only if performance actually becomes a problem. I'll take a look at implementing the debugging on/off functionality over the weekend. |
Regarding trying out complex painting, I could make a branch of the Robocode Bridge which can take advantage of the |
…m, and reusing the SVG loader context.
I just made debug graphics toggleable for each bot. The current implementation has the following behavior:
These days I don't work with Java a lot and I wasn't always 100% clear on which way of doing things is most in line with the design philosophy, so I'm sure the code isn't super clean. But at least it works ;) I think it'd be great to test more complex scenarios with the bridge. Regarding the coordinate transform, I think we should decide to either:
The first is definitely the most flexible way, but also more work than the second, since I expect there are going to be more bot APIs than there are viable GUIs. The current strategy is the second one, but only because it was the easiest for the proof of concept. I'm totally down to change that. Do you have a preference? |
…PO42/feat/graphical-debugging-svg # Conflicts: # gradle/libs.versions.toml
…nto fork/Cu3PO42/feat/graphical-debugging-svg # Conflicts: # server/src/main/kotlin/dev/robocode/tankroyale/server/connection/ClientWebSocketsHandler.kt
I have merged my work on the server, where the logging framework was changed, and fixed conflicts I caused due to that. It looks like you did a really good job with the recent changes as well, and you seem to hit the design philosophy pretty well, so no worries. 😊 coordinate transform A bot can query whether it is currently allowed to send debug graphics. It might be better to handle this in the bot internals and check if the bot is allowed to sent the debug painting or not. And you did a great job here by adding There is currently no onPaint I have not had a look and the server changes yet, but I need to dive into those as well. |
Thanks for taking care of that merge conflict! I have an idea for how to handle the text mirroring in the GUI. I'll take a look at it soon. However, I'm thinking of adding an escape hatch for advanced use cases where the bot can opt to handle everything itself. I agree that in most cases it would be fine for the bot to always paint and for the sake of simplicity, they probably should. However, I do have a use case for querying this. In a bot I developed for legacy Robocode, calculating the information needed for graphical debugging was quite computationally intensive and was not needed for normal operation. It is cases like this one I wanted to support with the method. The bot internals already do check this and do not send debugging information when the flag is false so as not to waste network traffic. |
…ised into bot-policy-update (BotPolicyUpdate) to support more flags in the future.
Okay. Let us keep the I did update the code with some of your changes: Please go ahead with the "mirrored text issue" if you have a good idea of how to fix it. I see that something is off with the server logging. 😢I will fix that! |
I like that change! My idea was to inject the following CSS styles into the debug graphics: text {
transform-origin: center center;
transform-box: fill-box;
transform: scaleY(-1);
} This works as expected in my web implementation, however JSvg does not yet support the required |
Controlling the text using CSS is a brilliant idea! 🤩 |
The idea explained above works as intended now. I have submitted a PR upstream at weisJ/jsvg#99, but until that is merged I have included a build of Jsvg in the repo. |
Nice! When we have everything needed in place, I think we should merge it into main as the PR is big. |
…phical-debugging-svg # Conflicts: # gradle/libs.versions.toml
Sure! I'm happy to go that route. I'm currently working to get that PR to Jsvg merged. |
# Conflicts: # gradle/libs.versions.toml
I fixed the issue with debugging graphics not working. |
Oooh, thank you so much! I see you moved the graphics to bot-state.schema. If I recall correctly, I specifically added it to the with-id schema so that the debug graphics would not be sent to other bots, but only controllers and observers. That said, there might be other mechanisms also making sure of that. I'm currently working on a bot API for Rust, so I likely won't get around to adding the code for .NET in the immediate future. I heard back from the JSvg maintainer and they are planning on a release in two weeks or so. |
Okay. I will have another look at this - as you are right that only controllers and observers should access the debugging graphics. In addition, I have some issues with the text, which I need to solve as well. The text is not being drawn/painted.
I look forward to see that. Reach out if you hit any obstacles so I can help you with fixing those. 😊
Nice. I will take note of this and incorporate the real version of Jsvg when it is ready. 👍 |
Huh, I'm happy to take a look here as well. It did render correctly when I implemented the mirroring code. Do you have a particular demo bot you're testing against?
Will do! I'm taking some notes as I'm developing it. It will look different than the existing APIs since Rust is a very different beast and many idioms won't translate. |
Regarding drawing a text, you just need to add this method to any bot you wish: @Override
public void onTick(TickEvent e) {
var g = getGraphics();
g.setColor(java.awt.Color.white);
g.setFont(new Font("Serif", Font.PLAIN, 12));
g.drawString("Hello Robocode", 50, 50);
// g.setColor(new java.awt.Color(255, 0, 0, 127));
// g.fillRect(100, 100, 25, 25);
} Rendering a rectangle with g.fillRect(...) works just fine |
# Conflicts: # bot-api/java/src/main/java/dev/robocode/tankroyale/botapi/BaseBot.java # bot-api/java/src/main/java/dev/robocode/tankroyale/botapi/IBaseBot.java # bot-api/java/src/main/java/dev/robocode/tankroyale/botapi/internal/BaseBotInternals.java # gradle/libs.versions.toml
@Cu3PO42 This is what I have used for debugging: SVG file with text flipping using transform-box: SVG file with text flipping with no transform-box to make a comparison: I checkout the main branch of Jsvg (with your changes) and using the TransformTest to write out the SVG processing as png files: try {
// File paths
String svgFilePath = "file:///C:\\Code\\jsvg\\jsvg\\src\\test\\resources\\com\\github\\weisj\\jsvg\\transform\\text-flipping-no-transformbox.svg";
String outputFilePath = "C:\\temp\\text-flipping-no-transformbox.png";
// Create a transcoder input
TranscoderInput input = new TranscoderInput(svgFilePath);
// Create an output stream to the PNG file
FileOutputStream outputStream = new FileOutputStream(outputFilePath);
TranscoderOutput output = new TranscoderOutput(outputStream);
// Create a PNG transcoder and configure it
PNGTranscoder transcoder = new PNGTranscoder();
// Perform the transcoding
transcoder.transcode(input, output);
// Close the output stream
outputStream.close();
System.out.println("SVG file converted to PNG successfully!");
} catch (Exception e) {
e.printStackTrace();
} I get this output png showing the issue for the one using the transform-box, showing that the text is not flipped around x or y: And this output png showing the correct/expected output, which is not using the transform-box: This is the SVG file for the one causing issues in Jsvg: <svg width="400" height="150" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
<style>
.normal {
transform-box: fill-box;
transform-origin: 50% 50%;
transform: scale(1, 1);
}
.flipped-around-x {
transform-box: fill-box;
transform-origin: 50% 50%;
transform: scaleX(-1);
}
.flipped-around-y {
transform-box: fill-box;
transform-origin: 50% 50%;
transform: scaleY(-1);
}
.flipped-around-x-and-y {
transform-box: fill-box;
transform-origin: 50% 50%;
transform: scale(-1, -1);
}
</style>
<text x="20" y="30" fill="green" font-size="20" class="normal">Normal text</text>
<text x="180" y="30" fill="blue" font-size="20" class="flipped-around-x">Flipped around X</text>
<text x="20" y="70" fill="red" font-size="20" class="flipped-around-y">Flipped around Y</text>
<text x="180" y="70" fill="magenta" font-size="20" class="flipped-around-x-and-y">Flipped around X and Y</text>
</svg> And here is the reference SVG: <svg width="400" height="150" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
<!-- Normal text -->
<text x="20" y="30" fill="green" font-size="20">Normal text</text>
<!-- Flipped around X axis - mirror horizontally -->
<text x="-321.11" y="30" fill="blue" font-size="20" transform="matrix(-1,0,0,1,0,0)">Flipped around X</text>
<!-- Flipped around Y axis - mirror vertically -->
<text x="20" y="-56.5" fill="red" font-size="20" transform="matrix(1,0,0,-1,0,0)">Flipped around Y</text>
<!-- Flipped around both X and Y axes -->
<text x="-373.65" y="-56.5" fill="magenta" font-size="20" transform="matrix(-1,0,0,-1,0,0)">Flipped around X and Y</text>
</svg> |
It looks like you are using Batik (not JSVG) to create the output png. Batik doesn't support the |
Hi! Sorry, real life caught up with me and I wasn't able to invest much (or really any) time here recently. I also cannot reproduce any issue directly with a recent build of JSvg. Since JSvg 1.7.0 is now in the standard Maven repos (thanks @weisJ !), I will try to update to that later and see if it resolves the issues you're experiencing in the GUI. I can't see any changes that should cause these; the rendering code there still uses the version of JSvg that I built. |
@flemming-n-larsen I believe Jannis' comment applied only to your test code. I can definitely confirm that the GUI does use JSvg for rendering and as far as I can tell, mirrorring is being applied: However, the location of the text was incorrect. Since I copied your rendering code, it should have been at (50, 50) in the bottom left. This is resolved after updating to 1.7.0. I believe I checked in a version of the library that didn't quite have all the changes yet. This is the new state: I just pushed a commit including the JSvg update. |
@Cu3PO42 Brilliant. I can confirm that the fix works for me as well now. This is really great news! I will soon merge this PR into main now, and start working on the C# part. |
Hello again!
I kept thinking about #114 and the more I did, the more I believe SVG is a perfectly good abstraction for transporting graphical debugging information that doesn't require inventing a new primitive language.
I wrote a prototype implementation to validate the approach and it's working great! This provides a full Graphics2D object on the bot side and draws the results in the GUI. I was also able to trivially add those graphics to my WIP web client. Since the Graphics2D API is available on the Java side, the bridge could also be extended with support for graphical debugging.
This prototype is not yet in a state to be merged, but I wanted to share my efforts. I would expect that the following items still need to be tackled:
Enabling/disabling graphical debugging
I see multiple ways forward here. We could add a switch in the GUI, similar to legacy Robocode and transmit this permission to the robot, which is then permitted to send debugging graphics that will be rendered.
Alternatively, a simple command line switch could be introduced that causes the GUI to draw debug information. A similar switch could then be added for the bot to send debug information. Unsolicited debug information would be dropped.
Extending the API
I believe it is reasonable to have the drawing APIs be different between platforms, so that each platform can rely on native libraries that integrate well with the language. If, however, you want to ensure a very similar API surface, one could introduce a simple SVG generator that allows drawing primitives such as rectangles, circles, etc. that could be ported to all platforms. Bot authors would still have the option of using a more powerful library.
Coordinate Transform
SVG's coordinate origin is in the top left, just like AWT's. Thus the transform you already have for Graphics2D in the context directly applies to the SVG as well and no work is needed on the side of the bot API. However, text requires special consideration so that it is not flipped upside down. This is not something I solved yet. I expect that injecting some CSS might help, but it may remain a hacky solution.
Another approach would be to not do anything on the side of the GUI and let the respective bot APIs handle it. This might be the more robust solution but potentially requires more code.
I'd be happy to hear your opinion on this approach!