Page MenuHomeWildfire Games

Add RL interface for Reinforcement Learning
ClosedPublic

Authored by irishninja on Aug 20 2019, 2:46 AM.

Details

Summary

This revision adds an interface for training reinforcement learning agents in 0 AD. This also includes a python wrapper for interacting with 0 AD including setting scenarios and controlling players in lock step.

The revision was originally written using gRPC but has since been updated to use mongoose (greatly simplifying the build process - thanks, @wraitii!).

An example using this to train RL agents with a variety of different state, action spaces can be found at https://github.com/brollb/simple-0ad-example.

Note: This does not contain an actual OpenAI gym environment as they require a clearly defined action/state space (which it is not obvious what the optimal representation is - or even if there is a single optimal representation). That said, it is easy to create a gym using the code in this revision and there are multiple examples in the beforementioned repository. A longer discussion about this can be found at https://github.com/0ad/0ad/pull/25#pullrequestreview-262056969

Test Plan

There is currently an example script demonstrating a lot of different supported actions which can be used to aid in manual testing. It still may be useful to add more test scripts for getting unit health and some of the other capabilities of the interface and wrapper.

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes

Successful build - Chance fights ever on the side of the prudent.

Linter detected issues:
Executing section Source...

source/simulation2/system/LocalTurnManager.h
|   1| /*·Copyright·(C)·2017·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2020" year instead of "2017"

source/simulation2/system/LocalTurnManager.h
|  59| The line belonging to the following result cannot be printed because it refers to a line that doesn't seem to exist in the given file.
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'classCTurnManager{' is invalid C code. Use --std or --language to configure the language.

source/simulation2/system/LocalTurnManager.cpp
|   1| /*·Copyright·(C)·2017·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2020" year instead of "2017"

source/rlinterface/RLInterface.h
|  40| »   std::string·content;
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'std::string' is invalid C code. Use --std or --language to configure the language.

source/simulation2/system/TurnManager.h
|  59| class·CTurnManager
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'classCTurnManager{' is invalid C code. Use --std or --language to configure the language.

source/main.cpp
|   0| }
|    | [NORMAL] CPPCheckBear (toomanyconfigs):
|    | Too many #ifdef configurations - cppcheck only checks 12 configurations. Use --force to check all configurations. For more details, use --enable=information.
Executing section JS...
Executing section cli...

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/2617/display/redirect

wraitii edited reviewers, added: Itms; removed: Restricted Owners Package.Jul 12 2020, 9:39 AM

However, I had avoided this earlier since I figured that this would result in double parsing of the actions which seemed a bit unnecessary

The reason why I'm suggesting a JSON parser is that you are parsing the request string using boost and regexes in RLMgCallback right now, which is inefficient (particularly include-wise).
It seems to me that using json_spirit would clean up that code nicely.

I don't think you need to understand JS::RootedValue and such for this, but you can give a look at this for a better understanding (in fact, the whole repo is interesting).


After rebasing the changes, I have been seeing the following linking error:

undefined reference to `typeinfo for PSERROR_Scripting_ConversionFailed'

The most likely issue in these cases is one of RTTI activation.
'typeinfo' is basically a struct with a lot of information on an object layout. It's generated when it's needed, which is either with some types of exceptions (iirc) or when RTTI is activated.

I'm not sure why you're getting this _now_ though. Have you tried cleaning the workspace and recompiling? You might have used one of my GUI patches that turns RTTI off in the past?

source/rlinterface/RLInterface.cpp
258

Probably yes.

331

I believe it will, yes.

Why do you think it might not work? Perhaps there's something more I can explain about how our simulation works :)

irishninja updated this revision to Diff 12764.Jul 18 2020, 2:49 PM

Remove regex, boost parsing. Still running into linking errors so need more testing but wanted to push these updates regardless to get feedback and see how it does in jenkins. (I suspect that the linking issues are specific to my machine...)

Build failure - The Moirai have given mortals hearts that can endure.

Link to build: https://jenkins.wildfiregames.com/job/vs2015-differential/2162/display/redirect

Successful build - Chance fights ever on the side of the prudent.

Linter detected issues:
Executing section Source...

source/simulation2/system/TurnManager.h
|  59| class·CTurnManager
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'classCTurnManager{' is invalid C code. Use --std or --language to configure the language.

source/rlinterface/RLInterface.h
|  40| »   std::string·content;
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'std::string' is invalid C code. Use --std or --language to configure the language.

source/simulation2/system/LocalTurnManager.cpp
|   1| /*·Copyright·(C)·2017·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2020" year instead of "2017"

source/simulation2/system/LocalTurnManager.h
|   1| /*·Copyright·(C)·2017·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2020" year instead of "2017"

source/simulation2/system/LocalTurnManager.h
|  59| The line belonging to the following result cannot be printed because it refers to a line that doesn't seem to exist in the given file.
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'classCTurnManager{' is invalid C code. Use --std or --language to configure the language.

source/main.cpp
|   0| }
|    | [NORMAL] CPPCheckBear (toomanyconfigs):
|    | Too many #ifdef configurations - cppcheck only checks 12 configurations. Use --force to check all configurations. For more details, use --enable=information.
Executing section JS...
Executing section cli...

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/2695/display/redirect

wraitii updated this revision to Diff 12818.Jul 20 2020, 4:34 PM

You were including #include "third_party/mongoose/mongoose.cpp", which probably is the source of your include error.

To get the POST buffer, what you should do instead is read the Content-Length header, using this:

const char* val = mg_get_header(conn, "Content-Length");
if (!val)
{
	mg_printf(conn, "%s", noPostData);
	return handled;
}
int bufSize = std::atoi(val);

I've taken the liberty of doing these changes myself, so reuploading. Hopefully this might fix the Window build.

wraitii added inline comments.Jul 20 2020, 4:36 PM
source/rlinterface/RLInterface.cpp
166

this is probably not useful, I needed it during debugging.

Build failure - The Moirai have given mortals hearts that can endure.

Link to build: https://jenkins.wildfiregames.com/job/vs2015-differential/2195/display/redirect

wraitii updated this revision to Diff 12820.Jul 20 2020, 4:45 PM

Remove GNU extension "char a[size]".

Successful build - Chance fights ever on the side of the prudent.

Linter detected issues:
Executing section Source...

source/simulation2/system/LocalTurnManager.cpp
|   1| /*·Copyright·(C)·2017·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2020" year instead of "2017"

source/simulation2/system/LocalTurnManager.h
|   1| /*·Copyright·(C)·2017·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2020" year instead of "2017"

source/simulation2/system/LocalTurnManager.h
|  59| The line belonging to the following result cannot be printed because it refers to a line that doesn't seem to exist in the given file.
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'classCTurnManager{' is invalid C code. Use --std or --language to configure the language.

source/simulation2/system/TurnManager.h
|  59| class·CTurnManager
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'classCTurnManager{' is invalid C code. Use --std or --language to configure the language.

source/rlinterface/RLInterface.h
|  40| »   std::string·content;
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'std::string' is invalid C code. Use --std or --language to configure the language.

source/main.cpp
|   0| }
|    | [NORMAL] CPPCheckBear (toomanyconfigs):
|    | Too many #ifdef configurations - cppcheck only checks 12 configurations. Use --force to check all configurations. For more details, use --enable=information.
Executing section JS...
Executing section cli...

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/2728/display/redirect

Successful build - Chance fights ever on the side of the prudent.

Linter detected issues:
Executing section Source...

source/simulation2/system/LocalTurnManager.cpp
|   1| /*·Copyright·(C)·2017·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2020" year instead of "2017"

source/rlinterface/RLInterface.h
|  40| »   std::string·content;
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'std::string' is invalid C code. Use --std or --language to configure the language.

source/simulation2/system/LocalTurnManager.h
|   1| /*·Copyright·(C)·2017·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2020" year instead of "2017"

source/simulation2/system/LocalTurnManager.h
|  59| The line belonging to the following result cannot be printed because it refers to a line that doesn't seem to exist in the given file.
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'classCTurnManager{' is invalid C code. Use --std or --language to configure the language.

source/simulation2/system/TurnManager.h
|  59| class·CTurnManager
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'classCTurnManager{' is invalid C code. Use --std or --language to configure the language.

source/main.cpp
|   0| }
|    | [NORMAL] CPPCheckBear (toomanyconfigs):
|    | Too many #ifdef configurations - cppcheck only checks 12 configurations. Use --force to check all configurations. For more details, use --enable=information.
Executing section JS...
Executing section cli...

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/2729/display/redirect

irishninja marked an inline comment as done.Jul 24 2020, 3:57 AM
irishninja added inline comments.
source/rlinterface/RLInterface.cpp
331

I had assumed that GUIInterfaceCalls required a GUI to be present. I don't fully understand how it all fits together just yet :/

After looking at the code a little more, I see an example where script calls are made from the GuiInterface for getting the replay metadata. Is this what you mean by GuiInterfaceCalls? It also looks like that example uses CmpPtr rather than GetEntitiesWithInterface and I don't quite understand how these are meant to be combined. I had been getting the AI interface but it seems here that I need entities with the AI interface (since I am going to be calling "GetFullRepresentation") but they also need to have the GuiInterface (since I need to make ScriptCalls).

I am sure I am misunderstanding but if you could provide some guidance or clarity, it would be much appreciated :)

As an example of what I have been playing around with. The following code fails as expected but might help illustrate how I understand the suggestion (incorrect but maybe it will be helpful):

CSimulation2* sim = g_Game->GetSimulation2();
CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_GuiInterface);
entity_id_t ent = ents.front().first;
const CSimContext simContext = g_Game->GetSimulation2()->GetSimContext();
CmpPtr<ICmpGuiInterface> cmpGuiInterface(simContext, ent);

ScriptInterface& scriptInterface = sim->GetScriptInterface();
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);

JS::RootedValue arg(cx);
JS::RootedValue state(cx);
cmpGuiInterface->ScriptCall(INVALID_PLAYER, L"GetFullRepresentation", arg, &state);  // This fails as the fn is only defined for the AI interface. Switching to use the AIInterface fails to compile since "ScriptCall" is not defined for ICmpAIInterface.
return scriptInterface.StringifyJSON(&state, false);
wraitii added inline comments.Jul 25 2020, 12:12 PM
source/rlinterface/RLInterface.cpp
331

I had assumed that GUIInterfaceCalls required a GUI to be present. I don't fully understand how it all fits together just yet :/

Indeed it doesn't.
To paint the global picture: the simulation JS context is distinct from the GUI JS context, to avoid issues with the GUI modifying the simulation. So the GUIInterface is a "Simulation" component, tasked with sending and receiving information to/from the GUI Context.

When you call something in the GuiInterface, you're calling the simulation.


Components can also be "system" or local to an entity. The GuiInterface is a "system" component. Your (understandable) mistake was thinking that each entity has a GuiInterface like they have an AIProxy. AIInterface is also a system component, by the way.


CmpPtr is just a C++ wrapper to a component pointer.

So to recap, what you need to do is something like this (compilation not guaranteed):

CSimulation2* sim = g_Game->GetSimulation2();
// Here I fetch a list of all entities in the game with an IID_AIProxy.
CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_AIProxy);
// Query the GUI Interface of the system.
CmpPtr<ICmpGuiInterface> cmpGuiInterface(sim, SYSTEM_ENTITY);

ScriptInterface& scriptInterface = sim->GetScriptInterface();
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);

for (entity_id_t ent : ents)
{
	// this is per-entity
	CmpPtr<ICmpAIProxy> cmpAIProxy(sim, ent);
	std::string test = cmpAIProxy->GetFullRepresentation();
}

cmpGuiInterface->ScriptCall(INVALID_PLAYER, L"GetSimulationState", arg, &state);

return scriptInterface.StringifyJSON(&state, false);

The thing is this still relies on AIProxy, so it's kind of the same as using AIInterface... And it need s aC++ AI Proxy component :/

So to be honest I'm not sure it's much better than what you have now, after all.

Itms requested changes to this revision.Jul 30 2020, 12:23 PM

Very fun tool to play with, great job! And the code looks pretty nice, it's self-contained and easy to read.

I have a few code nitpicks that I have listed.

On top of this, my only actual concern is a segfault I experience when I run the simple-example. When the run is finished, I get the popup saying I have won, allowing me to quit the game. When I hit yes, the game segfaults at

RLInterface::TryApplyMessage at ../../../source/ps/Game.h:174

I believe it happens because the python script still runs, but I'm not sure. Could you look into this? Thanks!

binaries/data/config/default.cfg
478

what does it mean to specify an address? it can only serve something on localhost, no? if I'm not mistaken the config option and the cli option should just be a port.

source/main.cpp
320

Even though it's generally considered bad practice, I would suggest matching the logic of the rest of main.cpp and use a g_RLInterface. To be honest the main reason is that I'm uncomfortable with overloading Frame: this file is mostly C so I'm perturbed to see C++ ?

If you follow the suggestion you can just replace if (using_interface) by if (g_RLInterface) which would match the style of the surrounding code.

The only thing to keep in mind is to free the pointer manually at the end of RunRLServer.

504

Shouldn't this line be outside the conditional, after the installation?

512

no braces

523

no braces

source/rlinterface/RLInterface.cpp
285

JSAutoRequest

314

Why did you add braces to the body of this case? They seem unneeded with the current code.

322

JSAutoRequest

354

JSAutoRequest

source/simulation2/system/TurnManager.h
27

unneeded anymore

source/tools/clients/python/.gitignore
1 ↗(On Diff #12820)

remove this file from the SVN commit
it's already in the top-level gitignore of the git mirror
when we commit this, we can add a svn:ignore property on the parent folder.

source/tools/clients/python/README.md
1 ↗(On Diff #12820)

I would call the directory source/tools/rlinterface or maybe rlclient but not clients.

23 ↗(On Diff #12820)

It should be 6000

32 ↗(On Diff #12820)

this is not in your API as of now

source/tools/clients/python/tests/test_actions.py
92 ↗(On Diff #12820)

The debug_print test passes, but it doesn't really work: it creates a 0 A.D. error.

This revision now requires changes to proceed.Jul 30 2020, 12:23 PM
irishninja updated this revision to Diff 12963.Jul 30 2020, 2:37 PM
irishninja marked 10 inline comments as done.

Updated the code following the review from Itms. I still need to check out the seg fault but everything else should be addressed!

irishninja added inline comments.Jul 30 2020, 2:39 PM
binaries/data/config/default.cfg
478

This is the IP that is accepting the connections and can be used to restrict external access. For example, if you specify "0.0.0.0:6000", it will allow allow all connections whereas "127.0.0.1:6000" will only allow connections from localhost.

source/main.cpp
504

This is actually based on https://github.com/0ad/0ad/blob/master/source/main.cpp#L617-L627 but I can certainly change it, if you would like.

source/rlinterface/RLInterface.cpp
314

Without the braces, I am getting redeclaration errors and a "jump to case label" error. Is there a better way I should be handling this?

source/simulation2/system/TurnManager.h
27

I seem to get the following error when I remove this include:

==== Building rlinterface (release) ====
RLInterface.cpp
In file included from ../../../source/rlinterface/RLInterface.h:30,
                 from ../../../source/rlinterface/RLInterface.cpp:17:
../../../source/simulation2/system/TurnManager.h:173:7: error: ‘deque’ in namespace ‘std’ does not name a template type
  173 |  std::deque<std::map<u32, std::vector<SimulationCommand>>> m_QueuedCommands;
      |       ^~~~~
../../../source/simulation2/system/TurnManager.h:22:1: note: ‘std::deque’ is defined in header ‘<deque>’; did you forget to ‘#include <deque>’?
   21 | #include "ps/CStr.h"
  +++ |+#include <deque>
   22 | #include "simulation2/helpers/SimulationCommand.h"
make[1]: *** [rlinterface.make:129: obj/rlinterface_Release/RLInterface.o] Error 1
make: *** [Makefile:83: rlinterface] Error 2

Am I missing something?

Successful build - Chance fights ever on the side of the prudent.

Linter detected issues:
Executing section Source...

source/simulation2/system/TurnManager.h
|  59| class·CTurnManager
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'classCTurnManager{' is invalid C code. Use --std or --language to configure the language.

source/simulation2/system/LocalTurnManager.cpp
|   1| /*·Copyright·(C)·2017·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2020" year instead of "2017"

source/simulation2/system/LocalTurnManager.h
|   1| /*·Copyright·(C)·2017·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2020" year instead of "2017"

source/simulation2/system/LocalTurnManager.h
|  59| The line belonging to the following result cannot be printed because it refers to a line that doesn't seem to exist in the given file.
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'classCTurnManager{' is invalid C code. Use --std or --language to configure the language.

source/rlinterface/RLInterface.h
|  40| »   std::string·content;
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'std::string' is invalid C code. Use --std or --language to configure the language.

source/main.cpp
|   0| }
|    | [NORMAL] CPPCheckBear (toomanyconfigs):
|    | Too many #ifdef configurations - cppcheck only checks 12 configurations. Use --force to check all configurations. For more details, use --enable=information.
Executing section JS...
Executing section cli...

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/2806/display/redirect

Itms added inline comments.Jul 30 2020, 7:44 PM
binaries/data/config/default.cfg
478

OK thanks I see ?

source/main.cpp
504

Oh I'm sorry I read too fast, I thought it was the activated mods or something.

In this case installedMods is not used in your code. you don't need to call GetInstalledMods and you don't need the vector itself, unless you decide to print a log message listing the installed mods.

source/rlinterface/RLInterface.cpp
314

Oh sorry I always forget that the compiler doesn't understand that break avoids the collision. The code is good then.

Just for the style, maybe put the break; inside the braces, and add the same braces to the next case, in case new cases are added in the future.

source/simulation2/system/TurnManager.h
27

Ah it looks like deque can be declared without the header, but not used. You can either move this include to RLInterface.cpp, or you can leave it here but in this case there will be an unneeded include in TurnManager.cpp, that you could remove. I would favor moving the include to your own code, that way, this commit touches one file less.

irishninja marked 2 inline comments as done.Jul 30 2020, 8:33 PM
irishninja added inline comments.
source/main.cpp
504

I believe it is used on line 517 with the call to InitGraphics (basically copied from the RunGameOrAtlas function). Should I remove it?

source/simulation2/system/TurnManager.h
27

It actually looks like there is no include in the cpp file. It seems to me like it would be better to have it in the TurnManager.h since it would be more clear why it is included rather than including it in RLInterface because TurnManager requires it.

That said, I am happy to defer to whatever you prefer!

irishninja updated this revision to Diff 12967.Jul 30 2020, 8:34 PM

Style fixes mostly from the last round of feedback. I am still digging into the seg fault from earlier. I had a couple questions about some of the earlier comments that I made inline and will update the code after I have a clearer idea of the approach to take!

Build failure - The Moirai have given mortals hearts that can endure.

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/2810/display/redirect

Itms added inline comments.Jul 30 2020, 10:10 PM
source/main.cpp
504

ahhh sorry I can't read :) looks good to me then!

source/simulation2/system/TurnManager.h
27

There must be a couple of includes somewhere else. Then it's probably better to include it in the main header like you did, I agree ?

irishninja marked 2 inline comments as done.

Fixed seg fault due to user exiting the game from the UI when the script is using it, too.

Build failure - The Moirai have given mortals hearts that can endure.

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/2814/display/redirect

Itms accepted this revision.Jul 31 2020, 9:30 AM

Thanks again for the great work ?

source/rlinterface/RLInterface.cpp
177

no braces ?

201

no braces here either

206

and there

This revision is now accepted and ready to land.Jul 31 2020, 9:30 AM
irishninja updated this revision to Diff 12978.Jul 31 2020, 2:36 PM

Fixed minor style issues

Successful build - Chance fights ever on the side of the prudent.

Linter detected issues:
Executing section Source...

source/simulation2/system/LocalTurnManager.h
|   1| /*·Copyright·(C)·2017·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2020" year instead of "2017"

source/simulation2/system/LocalTurnManager.h
|  59| The line belonging to the following result cannot be printed because it refers to a line that doesn't seem to exist in the given file.
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'classCTurnManager{' is invalid C code. Use --std or --language to configure the language.

source/rlinterface/RLInterface.h
|  40| »   std::string·content;
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'std::string' is invalid C code. Use --std or --language to configure the language.

source/simulation2/system/TurnManager.h
|  59| class·CTurnManager
|    | [MAJOR] CPPCheckBear (syntaxError):
|    | Code 'classCTurnManager{' is invalid C code. Use --std or --language to configure the language.

source/simulation2/system/LocalTurnManager.cpp
|   1| /*·Copyright·(C)·2017·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2020" year instead of "2017"

source/main.cpp
|   0| }
|    | [NORMAL] CPPCheckBear (toomanyconfigs):
|    | Too many #ifdef configurations - cppcheck only checks 12 configurations. Use --force to check all configurations. For more details, use --enable=information.
Executing section JS...
Executing section cli...

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/2815/display/redirect

wraitii updated this revision to Diff 12980.Aug 1 2020, 9:50 AM

Quick tweaks before merging.

wraitii accepted this revision.Aug 1 2020, 9:51 AM

Thanks again for the hard work and keeping up with this :) It's a great addition.

I'm not sure how much we want to put in rl_client and how much we want to keep in separate repositories, but I think there's room for a few more examples. We might consider opening a "0 A.D. RL repo" on the wildfire games organisation on GitHub (ping @Itms)
I did some minor tweaks before merging this:

  • Moved most header includes to the .cpp file -> this makes sure that people including RLInterface.h don't bring in a ton of un-needed headers.
  • Declared g_RLInterface in RLInterface.h, similar to what we do for g_Game.
Vulcan added a comment.Aug 1 2020, 9:52 AM

Build failure - The Moirai have given mortals hearts that can endure.

Link to build: https://jenkins.wildfiregames.com/job/vs2015-differential/2283/display/redirect

Vulcan added a comment.Aug 1 2020, 9:53 AM

Build failure - The Moirai have given mortals hearts that can endure.

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/2817/display/redirect

wraitii updated this revision to Diff 12982.Aug 1 2020, 10:08 AM

Should compile like this, went a bit overboard with the headers :p .

Build failure - The Moirai have given mortals hearts that can endure.

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/2819/display/redirect

Build failure - The Moirai have given mortals hearts that can endure.

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/2821/display/redirect

Build failure - The Moirai have given mortals hearts that can endure.

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/2822/display/redirect

Successful build - Chance fights ever on the side of the prudent.

Linter detected issues:

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/2824/display/redirect

This revision was landed with ongoing or failed builds.Aug 1 2020, 12:53 PM
This revision was automatically updated to reflect the committed changes.