From a43ebcdd642437078e7fdefa8e2eeeed5e95180f Mon Sep 17 00:00:00 2001 From: Mark Kropf Date: Thu, 18 Jun 2026 19:58:36 +0100 Subject: [PATCH 01/39] SolLua: use sol::lua_nil instead of the sol::nil alias A handful of SolLua bindings still referenced sol::nil / sol::type::nil while the rest of the same files already use sol::lua_nil. sol::nil is a thin alias for lua_nil (see lib/sol2/sol.hpp) and collides with the `nil` macro that Objective-C headers define, so lua_nil is the portable spelling. This brings the remaining spots in line with the prevailing convention. Extracted from cc3cad907f in #2991; cross-platform, no behavior change. --- rts/Rml/SolLua/bind/Context.cpp | 6 +++--- rts/Rml/SolLua/bind/Element.cpp | 2 +- rts/Rml/SolLua/bind/bind.cpp | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rts/Rml/SolLua/bind/Context.cpp b/rts/Rml/SolLua/bind/Context.cpp index db0dcb8960b..bd40fd4e535 100644 --- a/rts/Rml/SolLua/bind/Context.cpp +++ b/rts/Rml/SolLua/bind/Context.cpp @@ -131,7 +131,7 @@ struct lua_iterator_state sol::state_view l{s}; int index = 0; int count = keytable.size(); - while (keytable.get(++index).get_type() != sol::type::nil && index <= count) { + while (keytable.get(++index).get_type() != sol::type::lua_nil && index <= count) { this->keys.emplace_back(sol::object(l, sol::in_place, index)); } } else { @@ -199,7 +199,7 @@ createNewIndexFunction(std::shared_ptr data, const } if (value.is()) { auto value_raw = value.as().raw_get("__raw"); - if (value_raw != sol::nil && value_raw.is()) { + if (value_raw != sol::lua_nil && value_raw.is()) { // new value is a datamodel proxy, so get the underlying table to assign prop.as().raw_set(solkey, value_raw.as().call(value)); } else { @@ -290,7 +290,7 @@ sol::table openDataModel(Rml::Context& self, const Rml::String& name, sol::objec } if (value.is()) { auto value_raw = value.as().raw_get("__raw"); - if (value_raw != sol::nil && value_raw.is()) { + if (value_raw != sol::lua_nil && value_raw.is()) { // new value is a datamodel proxy, so get the underlying table to assign data->Table.raw_set(key, value_raw.as().call(value)); } else { diff --git a/rts/Rml/SolLua/bind/Element.cpp b/rts/Rml/SolLua/bind/Element.cpp index e2edf19159a..3bf3ecc7fea 100644 --- a/rts/Rml/SolLua/bind/Element.cpp +++ b/rts/Rml/SolLua/bind/Element.cpp @@ -158,7 +158,7 @@ namespace Rml::SolLua void Set(const sol::this_state L, const std::string& name, const sol::object& value) { - if (value.get_type() == sol::type::nil) { + if (value.get_type() == sol::type::lua_nil) { m_element->RemoveProperty(name); return; } diff --git a/rts/Rml/SolLua/bind/bind.cpp b/rts/Rml/SolLua/bind/bind.cpp index 21eabe9d46c..98ce9fec37c 100644 --- a/rts/Rml/SolLua/bind/bind.cpp +++ b/rts/Rml/SolLua/bind/bind.cpp @@ -39,7 +39,7 @@ namespace Rml::SolLua sol::object makeObjectFromVariant(const Rml::Variant* variant, sol::state_view s) { - if (!variant) return sol::make_object(s, sol::nil); + if (!variant) return sol::make_object(s, sol::lua_nil); switch (variant->GetType()) { @@ -69,10 +69,10 @@ namespace Rml::SolLua case Rml::Variant::VOIDPTR: return sol::make_object(s, variant->Get()); default: - return sol::make_object(s, sol::nil); + return sol::make_object(s, sol::lua_nil); } - return sol::make_object(s, sol::nil); + return sol::make_object(s, sol::lua_nil); } } // end namespace Rml::SolLua From 3e83e20c2805b09c1165442e4df428fcb935a7a5 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Thu, 18 Jun 2026 20:17:54 +0100 Subject: [PATCH 02/39] float3: drop redundant direct streflop_cond.h include float3.h included lib/streflop/streflop_cond.h directly, ahead of FastMath.h. FastMath.h defines MATH_SQRT_OVERRIDE before it includes streflop_cond.h (so streflop does not define its own math::sqrt(float) -- FastMath provides a faster one). The direct include meant streflop_cond.h could be processed before that override was set. Drop the direct include; FastMath.h pulls in streflop_cond.h transitively with the override in place. creg_cond.h keeps its original position -- it does not pull in streflop, so it does not need to move. Surfaced by the macOS port (#2991, commit cc3cad907f). Co-authored-by: Mark Kropf --- rts/System/float3.h | 1 - 1 file changed, 1 deletion(-) diff --git a/rts/System/float3.h b/rts/System/float3.h index f096c112c0d..bf47dd90c2a 100644 --- a/rts/System/float3.h +++ b/rts/System/float3.h @@ -9,7 +9,6 @@ #include #include "System/BranchPrediction.h" -#include "lib/streflop/streflop_cond.h" #include "System/creg/creg_cond.h" #include "System/FastMath.h" #include "System/type2.h" From 517608f4076b2071742badd6a90eab3dc6262828 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Fri, 19 Jun 2026 00:58:16 +0100 Subject: [PATCH 03/39] SafeUtil: include and directly (#3025) `SafeUtil.h` uses `` for `std::addressof`, and `` for `std::is_trivially_copyable` / `std::is_trivially_constructible_v`, but pulled them in only transitively. Include them directly so the header is self-contained and does not rely on include order elsewhere. Also use `std::is_trivially_default_constructible` for the default-construction. Extracted from cc3cad907f in #2991; cross-platform, no behavior change. Co-authored-by: Mark Kropf Co-authored-by: sprunk --- rts/System/SafeUtil.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rts/System/SafeUtil.h b/rts/System/SafeUtil.h index ce19b3f9738..9fbf946cc82 100644 --- a/rts/System/SafeUtil.h +++ b/rts/System/SafeUtil.h @@ -5,6 +5,8 @@ #include #include +#include +#include namespace spring { template inline void SafeDestruct(T*& p) @@ -112,8 +114,8 @@ namespace spring { static_assert(sizeof(TIn) == sizeof(TOut), "Types must match sizes"); static_assert(std::is_trivially_copyable::value , "Requires TriviallyCopyable input"); static_assert(std::is_trivially_copyable::value, "Requires TriviallyCopyable output"); - static_assert(std::is_trivially_constructible_v, - "This implementation additionally requires destination type to be trivially constructible"); + static_assert(std::is_trivially_default_constructible::value, + "This implementation additionally requires destination type to be trivially default-constructible"); TOut t2; std::memcpy(std::addressof(t2), std::addressof(t1), sizeof(TIn)); From 6318fb6c445daddeb1c9f4dea27e69bfb13847e1 Mon Sep 17 00:00:00 2001 From: iamaperson000 Date: Thu, 18 Jun 2026 19:49:28 +0100 Subject: [PATCH 04/39] LuaTextures: log glTexImage failures instead of returning nil silently LuaTextures::Create returned an empty string on glTexImage failure with no diagnostics. Log target/size/format/dataFormat/dataType/glError so texture-creation failures can be diagnosed. Extracted from 1e75080731 in #2991; cross-platform, no behavior change beyond the added log. --- rts/Lua/LuaTextures.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rts/Lua/LuaTextures.cpp b/rts/Lua/LuaTextures.cpp index 8d3a083dbef..0ae691d63c0 100644 --- a/rts/Lua/LuaTextures.cpp +++ b/rts/Lua/LuaTextures.cpp @@ -90,7 +90,9 @@ std::string LuaTextures::Create(const Texture& tex) } break; } - if (glGetError() != GL_NO_ERROR) { + if (const GLenum texErr = glGetError(); texErr != GL_NO_ERROR) { + LOG_L(L_ERROR, "[LuaTextures::%s] glTexImage failed: target=0x%x size=%dx%d fmt=0x%x dataFmt=0x%x dataType=0x%x border=%d glError=0x%x", + __func__, tex.target, tex.xsize, tex.ysize, tex.format, dataFormat, dataType, tex.border, texErr); glDeleteTextures(1, &texID); glBindTexture(tex.target, currentBinding); return ""; From 54348ff04d3fa6d1583224d3568cd2cf5825a2ab Mon Sep 17 00:00:00 2001 From: bruno-dasilva <8520801+bruno-dasilva@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:11:02 -0700 Subject: [PATCH 05/39] fix: guard against already-dead reclaim targets (#3020) Engine crash thread: https://discordapp.com/channels/549281623154229250/1516994684591931535 Bugged Scenario: A reclaimer A is guarding another reclaimer B, while both reclaiming the same wreck. A can be notified of wreck death BEFORE the guarded unit B is notified, leading to trying to reclaim the same (actively-deleting) wreck because B's reference hasn't been cleaned up yet. This leads to an eventual segfault. So: the short term/easy fix is to skip reclaiming of a dead/dying target. --- rts/Sim/Units/CommandAI/BuilderCAI.cpp | 4 ++-- rts/Sim/Units/UnitTypes/Builder.cpp | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/rts/Sim/Units/CommandAI/BuilderCAI.cpp b/rts/Sim/Units/CommandAI/BuilderCAI.cpp index b747e342615..e1faddb7dd6 100644 --- a/rts/Sim/Units/CommandAI/BuilderCAI.cpp +++ b/rts/Sim/Units/CommandAI/BuilderCAI.cpp @@ -891,13 +891,13 @@ void CBuilderCAI::ExecuteGuard(Command& c) StopSlowGuard(); } return; - } else if (b->curReclaim && owner->unitDef->canReclaim) { + } else if (b->curReclaim && !b->curReclaim->detached && owner->unitDef->canReclaim) { StopSlowGuard(); if (!ReclaimObject(b->curReclaim)) { StopMove(); } return; - } else if (b->curResurrect && owner->unitDef->canResurrect) { + } else if (b->curResurrect && !b->curResurrect->detached && owner->unitDef->canResurrect) { StopSlowGuard(); if (!ResurrectObject(b->curResurrect)) { StopMove(); diff --git a/rts/Sim/Units/UnitTypes/Builder.cpp b/rts/Sim/Units/UnitTypes/Builder.cpp index 4cf3240b1cd..deb8c846b73 100644 --- a/rts/Sim/Units/UnitTypes/Builder.cpp +++ b/rts/Sim/Units/UnitTypes/Builder.cpp @@ -605,6 +605,15 @@ void CBuilder::SetRepairTarget(CUnit* target) void CBuilder::SetReclaimTarget(CSolidObject* target) { RECOIL_DETAILED_TRACY_ZONE; + // A target already being destroyed (detached) cannot have a death dependence + // registered (AddDeathDependence no-ops on detached objects), which would leave + // curReclaim dangling. This happens when ~CObject's DependentDied cascade re-enters + // reclaim logic mid-deletion (e.g. CBuilderCAI::ExecuteGuard reading a guardee's + // stale curReclaim) on the very feature being freed. Refuse the dead target. + assert(target != nullptr); + if (target->detached) + return; + if (dynamic_cast(target) != nullptr && !static_cast(target)->def->reclaimable) return; @@ -630,6 +639,11 @@ void CBuilder::SetReclaimTarget(CSolidObject* target) void CBuilder::SetResurrectTarget(CFeature* target) { RECOIL_DETAILED_TRACY_ZONE; + // see SetReclaimTarget: never depend on an object that is already being destroyed + assert(target != nullptr); + if (target->detached) + return; + if (curResurrect == target || target->udef == nullptr) return; @@ -646,6 +660,11 @@ void CBuilder::SetResurrectTarget(CFeature* target) void CBuilder::SetCaptureTarget(CUnit* target) { RECOIL_DETAILED_TRACY_ZONE; + // see SetReclaimTarget: never depend on an object that is already being destroyed + assert(target != nullptr); + if (target->detached) + return; + if (target == curCapture) return; From e91a58f5c57dcdd25df3e51aac911ce3b481179f Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Sat, 20 Jun 2026 14:14:07 +0100 Subject: [PATCH 06/39] CUtils: use const dirent* for the scandir selector on all platforms util_fileSelector was declared with a non-const struct dirent* on __APPLE__ and const elsewhere. macOS scandir() expects the selector argument as int(*)(const struct dirent*), so the non-const Apple variant failed to compile under GCC: Util.c:500: error: passing argument 3 of 'scandir' from incompatible pointer type Both branches were otherwise identical, so drop the __APPLE__ split and use const struct dirent* unconditionally (matches POSIX scandir). No effect on Linux/Windows. Assisted by Claude Code; verified by compiling the macOS headless build. --- AI/Wrappers/CUtils/Util.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/AI/Wrappers/CUtils/Util.c b/AI/Wrappers/CUtils/Util.c index c33d2e418e8..4764a619900 100644 --- a/AI/Wrappers/CUtils/Util.c +++ b/AI/Wrappers/CUtils/Util.c @@ -487,11 +487,7 @@ static void util_initFileSelector(const char* suffix) { fileSelectorSuffix = suffix; } -#if defined(__APPLE__) -static int util_fileSelector(struct dirent* fileDesc) { -#else static int util_fileSelector(const struct dirent* fileDesc) { -#endif return util_endsWith(fileDesc->d_name, fileSelectorSuffix); } From 580100a0dfeb3fb2f6203ad0cf7bf077f212b6d4 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Sat, 20 Jun 2026 14:12:15 +0100 Subject: [PATCH 07/39] legacy: only require X11 on non-Apple UNIX The legacy build did find_package(X11 REQUIRED) under a plain if(UNIX) guard. macOS is UNIX in CMake but does not use X11 (it uses Cocoa), so configuring the engine on macOS failed at find_package(X11). An if(APPLE) block already follows for Foundation, so exclude Apple from the X11 branch: if(UNIX AND NOT APPLE). Surfaced configuring the spring-headless target on macOS (which reuses the legacy Game target). No effect on Linux/Windows. Assisted by Claude Code; verified by configuring the macOS build. --- rts/builds/legacy/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rts/builds/legacy/CMakeLists.txt b/rts/builds/legacy/CMakeLists.txt index cbcc906f83d..f1d19ede7e3 100644 --- a/rts/builds/legacy/CMakeLists.txt +++ b/rts/builds/legacy/CMakeLists.txt @@ -49,7 +49,7 @@ find_freetype_hack() # hack to find different named freetype.dll find_package_static(Freetype 2.8.1 REQUIRED) list(APPEND engineLibraries Freetype::Freetype) -if (UNIX) +if (UNIX AND NOT APPLE) find_package(X11 REQUIRED) target_link_libraries(Game PRIVATE X11::Xcursor) list(APPEND engineLibraries ${X11_Xcursor_LIB} ${X11_X11_LIB}) From d13dcd341361637cdf1ab4e65b0e8d6fa7b5ca21 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Sat, 20 Jun 2026 13:10:24 +0100 Subject: [PATCH 08/39] FindSevenZip: also accept the 7zz binary name Modern 7-Zip ships its CLI as '7zz' (Homebrew's 'sevenzip' formula installs /opt/homebrew/bin/7zz; recent Linux distros likewise package '7zz'). FindSevenZip only searched for '7z'/'7za', so configure failed with 'Could NOT find SevenZip (missing: SEVENZIP_BIN)' on such systems. Add '7zz' to the searched NAMES. No effect where 7z/7za already exist. --- rts/build/cmake/FindSevenZip.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rts/build/cmake/FindSevenZip.cmake b/rts/build/cmake/FindSevenZip.cmake index 1077ec7b17e..0e3e4002a67 100644 --- a/rts/build/cmake/FindSevenZip.cmake +++ b/rts/build/cmake/FindSevenZip.cmake @@ -23,7 +23,7 @@ ENDIF (SEVENZIP_BIN) set(progfilesx86 "ProgramFiles(x86)") find_program(SEVENZIP_BIN - NAMES 7z 7za + NAMES 7z 7za 7zz HINTS "${MINGWDIR}" "${MINGWLIBS}/bin" "$ENV{${progfilesx86}}/7-zip" "$ENV{ProgramFiles}/7-zip" "$ENV{ProgramW6432}/7-zip" PATH_SUFFIXES bin DOC "7zip executable" From 0da81b433a8c4e228a4ac02ac2e943bd0c5aa078 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Fri, 19 Jun 2026 23:55:16 +0100 Subject: [PATCH 09/39] macOS: remove dead SDL 1.2-era SDLMain.{m,h} rts/System/Platform/Mac/SDLMain.m and SDLMain.h are the classic SDL 1.2 Cocoa main wrapper (the Darrell Walisser / Max Horn template). They are: - not listed in any CMakeLists (never compiled) - not #included by any source file - built on Carbon (), which is 32-bit-only and unavailable on modern macOS / Apple Silicon SDL2 provides its own SDL_main, so this wrapper is obsolete. Remove the dead files so the macOS platform layer reflects what is actually built. --- rts/System/Platform/Mac/SDLMain.h | 18 -- rts/System/Platform/Mac/SDLMain.m | 299 ------------------------------ 2 files changed, 317 deletions(-) delete mode 100644 rts/System/Platform/Mac/SDLMain.h delete mode 100644 rts/System/Platform/Mac/SDLMain.m diff --git a/rts/System/Platform/Mac/SDLMain.h b/rts/System/Platform/Mac/SDLMain.h deleted file mode 100644 index 995d036c3f0..00000000000 --- a/rts/System/Platform/Mac/SDLMain.h +++ /dev/null @@ -1,18 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -/* - * SDLMain.m - main entry point for our Cocoa-ized SDL app - * Initial Version: Darrell Walisser - * Non-NIB-Code & other changes: Max Horn - * Feel free to customize this file to suit your needs. - */ - -#ifdef __APPLE__ - -#import - -@interface SDLMain : NSObject -@end - -#endif - diff --git a/rts/System/Platform/Mac/SDLMain.m b/rts/System/Platform/Mac/SDLMain.m deleted file mode 100644 index 2fea69074ad..00000000000 --- a/rts/System/Platform/Mac/SDLMain.m +++ /dev/null @@ -1,299 +0,0 @@ -/* SDLMain.m - main entry point for our Cocoa-ized SDL app - Initial Version: Darrell Walisser - Non-NIB-Code & other changes: Max Horn - - Feel free to customize this file to suit your needs -*/ - -#import "SDL.h" -#import "SDLMain.h" -#import /* for MAXPATHLEN */ -#import -#import - -/* Use this flag to determine whether we use SDLMain.nib or not */ -#define SDL_USE_NIB_FILE 0 - - -static int gArgc; -static char **gArgv; -static BOOL gFinderLaunch; - -//extern NSAutoreleasePool *pool; -//void PreInitMac(); - -void MacMessageBox(const char *msg, const char *caption, unsigned int flags){ - NSAlert *alert = [[[NSAlert alloc] init] autorelease]; - [alert addButtonWithTitle:@"OK"]; - [alert setMessageText:[NSString stringWithCString:caption]]; - [alert setInformativeText:[NSString stringWithCString:msg]]; - [alert setAlertStyle:NSWarningAlertStyle]; - [alert runModal]; -} - -#if SDL_USE_NIB_FILE -/* A helper category for NSString */ -@interface NSString (ReplaceSubString) -- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; -@end -#else -/* An internal Apple class used to setup Apple menus */ -@interface NSAppleMenuController:NSObject {} -- (void)controlMenu:(NSMenu *)aMenu; -@end -#endif - -@interface SDLApplication : NSApplication -@end - -@implementation SDLApplication -/* Invoked from the Quit menu item */ -- (void)terminate:(id)sender -{ - /* Post a SDL_QUIT event */ - SDL_Event event; - event.type = SDL_QUIT; - SDL_PushEvent(&event); -} -@end - - -/* The main class of the application, the application's delegate */ -@implementation SDLMain - -/* Set the working directory to the .app's parent directory */ -- (void) setupWorkingDirectory:(BOOL)shouldChdir -{ - char parentdir[MAXPATHLEN]; - char *c; - - strncpy ( parentdir, gArgv[0], sizeof(parentdir) ); - c = (char*) parentdir; - - while (*c != '\0') /* go to end */ - c++; - - while (*c != '/') /* back up to parent */ - c--; - - *c++ = '\0'; /* cut off last part (binary name) */ - - if (shouldChdir) - { - assert ( chdir (parentdir) == 0 ); /* chdir to the binary app's parent */ - assert ( chdir ("../../../") == 0 ); /* chdir to the .app's parent */ - } -} - -#if SDL_USE_NIB_FILE - -/* Fix menu to contain the real app name instead of "SDL App" */ -- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName -{ - NSRange aRange; - NSEnumerator *enumerator; - NSMenuItem *menuItem; - - aRange = [[aMenu title] rangeOfString:@"SDL App"]; - if (aRange.length != 0) - [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; - - enumerator = [[aMenu itemArray] objectEnumerator]; - while ((menuItem = [enumerator nextObject])) - { - aRange = [[menuItem title] rangeOfString:@"SDL App"]; - if (aRange.length != 0) - [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; - if ([menuItem hasSubmenu]) - [self fixMenu:[menuItem submenu] withAppName:appName]; - } - [ aMenu sizeToFit ]; -} - -#else - -void setupAppleMenu(void) -{ - /* warning: this code is very odd */ - NSAppleMenuController *appleMenuController; - NSMenu *appleMenu; - NSMenuItem *appleMenuItem; - - appleMenuController = [[NSAppleMenuController alloc] init]; - appleMenu = [[NSMenu alloc] initWithTitle:@""]; - appleMenuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; - - [appleMenuItem setSubmenu:appleMenu]; - - /* yes, we do need to add it and then remove it -- - if you don't add it, it doesn't get displayed - if you don't remove it, you have an extra, titleless item in the menubar - when you remove it, it appears to stick around - very, very odd */ - [[NSApp mainMenu] addItem:appleMenuItem]; - [appleMenuController controlMenu:appleMenu]; - [[NSApp mainMenu] removeItem:appleMenuItem]; - [appleMenu release]; - [appleMenuItem release]; -} - -/* Create a window menu */ -void setupWindowMenu(void) -{ - NSMenu *windowMenu; - NSMenuItem *windowMenuItem; - NSMenuItem *menuItem; - - - windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; - - /* "Minimize" item */ - menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; - [windowMenu addItem:menuItem]; - [menuItem release]; - - /* Put menu into the menubar */ - windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; - [windowMenuItem setSubmenu:windowMenu]; - [[NSApp mainMenu] addItem:windowMenuItem]; - - /* Tell the application object that this is now the window menu */ - [NSApp setWindowsMenu:windowMenu]; - - /* Finally give up our references to the objects */ - [windowMenu release]; - [windowMenuItem release]; -} - -/* Replacement for NSApplicationMain */ -void CustomApplicationMain () -{ - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - SDLMain *sdlMain; - //PreInitMac(); - - /* Ensure the application object is initialised */ - [SDLApplication sharedApplication]; - - /* Set up the menubar */ - [NSApp setMainMenu:[[NSMenu alloc] init]]; - setupAppleMenu(); - setupWindowMenu(); - - /* Create SDLMain and make it the app delegate */ - sdlMain = [[SDLMain alloc] init]; - [NSApp setDelegate:sdlMain]; - - /* Bring the app to foreground */ - ProcessSerialNumber psn; - GetCurrentProcess(&psn); - TransformProcessType(&psn,kProcessTransformToForegroundApplication); - - /* Start the main event loop */ - [NSApp run]; - - [sdlMain release]; - [pool release]; -} - -#endif - -/* Called when the internal event loop has just started running */ -- (void) applicationDidFinishLaunching: (NSNotification *) note -{ - int status; - - /* Set the working directory to the .app's parent directory */ - [self setupWorkingDirectory:gFinderLaunch]; - -#if SDL_USE_NIB_FILE - /* Set the main menu to contain the real app name instead of "SDL App" */ - [self fixMenu:[NSApp mainMenu] withAppName:[[NSProcessInfo processInfo] processName]]; -#endif - - /* Hand off to main application code */ - status = SDL_main (gArgc, gArgv); - - /* We're done, thank you for playing */ - exit(status); -} -@end - - -@implementation NSString (ReplaceSubString) - -- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString -{ - unsigned int bufferSize; - unsigned int selfLen = [self length]; - unsigned int aStringLen = [aString length]; - unichar *buffer; - NSRange localRange; - NSString *result; - - bufferSize = selfLen + aStringLen - aRange.length; - buffer = NSAllocateMemoryPages(bufferSize*sizeof(unichar)); - - /* Get first part into buffer */ - localRange.location = 0; - localRange.length = aRange.location; - [self getCharacters:buffer range:localRange]; - - /* Get middle part into buffer */ - localRange.location = 0; - localRange.length = aStringLen; - [aString getCharacters:(buffer+aRange.location) range:localRange]; - - /* Get last part into buffer */ - localRange.location = aRange.location + aRange.length; - localRange.length = selfLen - localRange.location; - [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; - - /* Build output string */ - result = [NSString stringWithCharacters:buffer length:bufferSize]; - - NSDeallocateMemoryPages(buffer, bufferSize); - - return result; -} - -@end - - - -#ifdef main -# undef main -#endif - - -/* Main entry point to executable - should *not* be SDL_main! */ -int main (int argc, char **argv) -{ - - /* Copy the arguments into a global variable */ - int i; - - /* This is passed if we are launched by double-clicking */ - if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { - gArgc = 1; - gFinderLaunch = YES; - } else { - gArgc = argc; - gFinderLaunch = NO; - } - gArgv = (char**) malloc (sizeof(*gArgv) * (gArgc+1)); - assert (gArgv != NULL); - for (i = 0; i < gArgc; i++) - gArgv[i] = argv[i]; - gArgv[i] = NULL; - -#if SDL_USE_NIB_FILE - [SDLApplication poseAsClass:[NSApplication class]]; - NSApplicationMain (); -#else - CustomApplicationMain (); -#endif - return 0; -} - - From 83642b066f755ac182ef97ea540569c07e59d2ff Mon Sep 17 00:00:00 2001 From: Scary le Poo Date: Sun, 21 Jun 2026 13:12:50 -0700 Subject: [PATCH 10/39] Fix SplinterFaction card link format Updated the link for the SplinterFaction card to include the 'https://' prefix. This has been broken for a very long time. I've asked Skyrbunny to fix it multiple times, but it has never gotten fixed. --- doc/site/content/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/site/content/_index.md b/doc/site/content/_index.md index fcbbe83428f..0d0a8d3b14b 100644 --- a/doc/site/content/_index.md +++ b/doc/site/content/_index.md @@ -19,7 +19,7 @@ draft = false {{< /cards >}} {{< cards >}} {{< card title="Tech Annihilation" image="/showcase/ta.jpeg" link="https://github.com/techannihilation/TA" >}} -{{< card title="SplinterFaction" image="showcase/splinter_faction.jpg" link="splinterfaction.info" >}} +{{< card title="SplinterFaction" image="showcase/splinter_faction.jpg" link="https://splinterfaction.info" >}} {{< card title="Mechcommander: Legacy" image="/showcase/mcl.jpg" link="https://github.com/SpringMCLegacy/SpringMCLegacy/wiki" >}} {{< /cards >}} From 13b6804534543c061d0eb8c06af438cb7d2a1c64 Mon Sep 17 00:00:00 2001 From: bruno-dasilva <8520801+bruno-dasilva@users.noreply.github.com> Date: Mon, 22 Jun 2026 12:38:03 -0700 Subject: [PATCH 11/39] fix: update section min-level in place instead of appending duplicates (#3052) setMinLevel() only searched for an existing entry on the "set back to default" (erase) path. Setting a section to a *non-default* level appended a new row unconditionally, so repeatedly changing one section's level (e.g. via Spring.SetLogSectionFilterLevel) accumulated duplicate entries and eventually filled the fixed 64-slot sectionMinLevels table -- after which every section-level change silently failed with "too many section-levels". Fix: Look the section up before appending and, if it already has an entry, update it in place. This bounds the table at one entry per section. This likely never happens in practice but this was found while writing other tests Co-authored-by: Bruno Da Silva Co-authored-by: Claude Opus 4.8 (1M context) --- rts/System/Log/DefaultFilter.cpp | 21 +++++++++++++------ test/engine/System/Log/TestILog.cpp | 31 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/rts/System/Log/DefaultFilter.cpp b/rts/System/Log/DefaultFilter.cpp index cf0c3939d29..3ec33410cf1 100644 --- a/rts/System/Log/DefaultFilter.cpp +++ b/rts/System/Log/DefaultFilter.cpp @@ -157,13 +157,15 @@ void log_filter_section_setMinLevel(int level, const char* section) // (same string but will not become garbage) section = *registeredSection; - if (level == log_filter_section_getDefaultMinLevel(section)) { - using P = decltype(log_filter::sectionMinLevels)::value_type; - - const auto sectionComparer = [](const P& a, const P& b) { return (log_filter_section_compare()(a.first, b.first)); }; - const auto sectionMinLevel = std::lower_bound(secLvls.begin(), secLvls.begin() + log_filter::numLevels, P{section, 0}, sectionComparer); + // locate any existing override for this section (the array is kept sorted) + using P = decltype(log_filter::sectionMinLevels)::value_type; + const auto sectionComparer = [](const P& a, const P& b) { return (log_filter_section_compare()(a.first, b.first)); }; + const auto sectionMinLevel = std::lower_bound(secLvls.begin(), secLvls.begin() + log_filter::numLevels, P{section, 0}, sectionComparer); + const bool exists = (sectionMinLevel != (secLvls.begin() + log_filter::numLevels) && strcmp(sectionMinLevel->first, section) == 0); - if (sectionMinLevel == (secLvls.begin() + log_filter::numLevels) || strcmp(sectionMinLevel->first, section) != 0) + if (level == log_filter_section_getDefaultMinLevel(section)) { + // back to default: drop any existing override (nothing to do otherwise) + if (!exists) return; // erase @@ -175,6 +177,13 @@ void log_filter_section_setMinLevel(int level, const char* section) return; } + // non-default: update any existing override in-place + if (exists) { + sectionMinLevel->second = level; + return; + } + + // add a net new override secLvls[log_filter::numLevels++] = {section, level}; // swap into position diff --git a/test/engine/System/Log/TestILog.cpp b/test/engine/System/Log/TestILog.cpp index cb8d4b3e4a1..7b7d1345f0c 100644 --- a/test/engine/System/Log/TestILog.cpp +++ b/test/engine/System/Log/TestILog.cpp @@ -3,6 +3,7 @@ #include "System/Log/FileSink.h" #include "System/Log/StreamSink.h" #include "System/Log/LogUtil.h" +#include "System/Log/DefaultFilter.h" #include @@ -231,3 +232,33 @@ TEST_CASE("IsEnabled") TLOG_SL( "other-one-time-section", L_DEBUG, "Testing LOG_IS_ENABLED_S"); } + +// Regression for the duplicate-entry leak in log_filter_section_setMinLevel. +// Setting a section to a non-default level used to *append* a new row every call +// instead of updating the existing one, so repeated changes to one section filled +// the fixed-size sectionMinLevels table and then made *all* section-level changes +// silently fail ("too many section-levels"). +TEST_CASE("SectionMinLevelNoDuplicateLeak") +{ + // non-default levels for these (non-default) sections; restored at the end + const int savedDefined = log_filter_section_getMinLevel(LOG_SECTION_DEFINED); + const int savedOneTime = log_filter_section_getMinLevel(LOG_SECTION_ONE_TIME_0); + + // hammer one section far more than the table could ever hold + for (int i = 0; i < 300; ++i) + log_filter_section_setMinLevel((i & 1) ? LOG_LEVEL_WARNING : LOG_LEVEL_ERROR, LOG_SECTION_DEFINED); + + // the most recent value wins (a single, updated-in-place entry) + log_filter_section_setMinLevel(LOG_LEVEL_ERROR, LOG_SECTION_DEFINED); + CHECK(log_filter_section_getMinLevel(LOG_SECTION_DEFINED) == LOG_LEVEL_ERROR); + + // and a *different* section must still be settable: with the old append bug + // the table is saturated by now and this set would be dropped + log_filter_section_setMinLevel(LOG_LEVEL_WARNING, LOG_SECTION_ONE_TIME_0); + CHECK(log_filter_section_getMinLevel(LOG_SECTION_ONE_TIME_0) == LOG_LEVEL_WARNING); + + // restore original levels (setting back to default takes the erase path) + log_filter_section_setMinLevel(savedDefined, LOG_SECTION_DEFINED); + log_filter_section_setMinLevel(savedOneTime, LOG_SECTION_ONE_TIME_0); +} + From 1caa3c52612c9741238f7a086e6d8696c7e77289 Mon Sep 17 00:00:00 2001 From: devgit283 Date: Tue, 23 Jun 2026 09:29:18 +0200 Subject: [PATCH 12/39] Convert `u8string_view` to `string` using explicit size The original code relied on implicit null-termination which is not guaranteed. --- rts/System/FileSystem/FileSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rts/System/FileSystem/FileSystem.cpp b/rts/System/FileSystem/FileSystem.cpp index d1471810f48..f319d2199ca 100644 --- a/rts/System/FileSystem/FileSystem.cpp +++ b/rts/System/FileSystem/FileSystem.cpp @@ -61,7 +61,7 @@ namespace Impl { return std::string(reinterpret_cast(utf8.c_str())); } RECOIL_FORCE_INLINE std::string StoreUTF8AsString(const std::u8string_view& utf8) { - return std::string(reinterpret_cast(utf8.data())); + return std::string(reinterpret_cast(utf8.data()), utf8.size()); } RECOIL_FORCE_INLINE std::string StorePathAsString(const fs::path& path) { return StoreUTF8AsString(path.u8string()); From 2186ee490c027eb2993ce2f67b98d4978ded142e Mon Sep 17 00:00:00 2001 From: devgit283 Date: Fri, 5 Jun 2026 21:57:29 +0200 Subject: [PATCH 13/39] Fix inconsistent class/struct forward declarations Aligns forward declarations with definitions to silence MSVC warnings. --- rts/Rendering/IconHandler.h | 2 +- rts/Sim/Misc/SmoothHeightMesh.h | 2 +- rts/Sim/MoveTypes/Components/MoveTypesComponents.h | 4 ++-- rts/Sim/MoveTypes/MoveDefHandler.h | 2 +- rts/Sim/MoveTypes/Utils/UnitTrapCheckUtils.h | 4 ++-- rts/Sim/Path/HAPFS/PathSearch.h | 2 +- rts/Sim/Path/QTPFS/Components/PathSpeedModInfo.h | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/rts/Rendering/IconHandler.h b/rts/Rendering/IconHandler.h index 6c67749d739..ec0e5bce6e3 100644 --- a/rts/Rendering/IconHandler.h +++ b/rts/Rendering/IconHandler.h @@ -13,7 +13,7 @@ #include "Rendering/GL/RenderBuffersFwd.h" #include "Rendering/Textures/TextureAtlas.h" -class UnitDef; +struct UnitDef; class CTextureRenderAtlas; namespace icon { diff --git a/rts/Sim/Misc/SmoothHeightMesh.h b/rts/Sim/Misc/SmoothHeightMesh.h index 888686af138..91866ddfb6c 100644 --- a/rts/Sim/Misc/SmoothHeightMesh.h +++ b/rts/Sim/Misc/SmoothHeightMesh.h @@ -22,7 +22,7 @@ namespace SmoothHeightMeshNamespace { */ class SmoothHeightMesh { - friend class SmoothHeightMeshDrawer; + friend struct SmoothHeightMeshDrawer; public: diff --git a/rts/Sim/MoveTypes/Components/MoveTypesComponents.h b/rts/Sim/MoveTypes/Components/MoveTypesComponents.h index b0d1d8b4265..fa7380707bb 100644 --- a/rts/Sim/MoveTypes/Components/MoveTypesComponents.h +++ b/rts/Sim/MoveTypes/Components/MoveTypesComponents.h @@ -7,8 +7,8 @@ #include "System/Ecs/Components/BaseComponents.h" #include -struct CUnit; -struct CFeature; +class CUnit; +class CFeature; namespace MoveTypes { diff --git a/rts/Sim/MoveTypes/MoveDefHandler.h b/rts/Sim/MoveTypes/MoveDefHandler.h index fbec124d13c..9c8d1db5601 100644 --- a/rts/Sim/MoveTypes/MoveDefHandler.h +++ b/rts/Sim/MoveTypes/MoveDefHandler.h @@ -18,7 +18,7 @@ class CUnit; class LuaTable; namespace MoveTypes { - class CheckCollisionQuery; + struct CheckCollisionQuery; } namespace MoveDefs { diff --git a/rts/Sim/MoveTypes/Utils/UnitTrapCheckUtils.h b/rts/Sim/MoveTypes/Utils/UnitTrapCheckUtils.h index 7db24ab3835..da7354b01fb 100644 --- a/rts/Sim/MoveTypes/Utils/UnitTrapCheckUtils.h +++ b/rts/Sim/MoveTypes/Utils/UnitTrapCheckUtils.h @@ -3,8 +3,8 @@ #ifndef UNIT_TRAP_CHECK_UTILS_H__ #define UNIT_TRAP_CHECK_UTILS_H__ -struct CFeature; -struct CUnit; +class CFeature; +class CUnit; namespace MoveTypes { void RegisterFeatureForUnitTrapCheck(CFeature* object); diff --git a/rts/Sim/Path/HAPFS/PathSearch.h b/rts/Sim/Path/HAPFS/PathSearch.h index 1fae37cc5dd..fdb3bfc7d85 100644 --- a/rts/Sim/Path/HAPFS/PathSearch.h +++ b/rts/Sim/Path/HAPFS/PathSearch.h @@ -6,7 +6,7 @@ #include "System/float3.h" class CSolidObject; -class MoveDef; +struct MoveDef; namespace HAPFS { struct PathSearch { diff --git a/rts/Sim/Path/QTPFS/Components/PathSpeedModInfo.h b/rts/Sim/Path/QTPFS/Components/PathSpeedModInfo.h index 19f7c9285cc..4bde1a878b5 100644 --- a/rts/Sim/Path/QTPFS/Components/PathSpeedModInfo.h +++ b/rts/Sim/Path/QTPFS/Components/PathSpeedModInfo.h @@ -12,7 +12,7 @@ namespace QTPFS { -class INode; +struct INode; struct NodeLayerSpeedInfoSweep { static constexpr std::size_t page_size = MoveDefHandler::MAX_MOVE_DEFS; From a854046cef9082d73bac86d19c020e4babac4592 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Thu, 25 Jun 2026 15:56:49 +0100 Subject: [PATCH 14/39] macOS: guard X11/Xlib.h include for non-X11 platforms SpringApp.cpp included for every non-Windows platform, but macOS has no X11 headers by default, breaking the native build. The only user of it, XInitThreads(), is already excluded on __APPLE__ at its call site, so guard the include the same way. --- rts/System/SpringApp.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rts/System/SpringApp.cpp b/rts/System/SpringApp.cpp index e5a011b19c3..a2274b6f9e5 100644 --- a/rts/System/SpringApp.cpp +++ b/rts/System/SpringApp.cpp @@ -13,7 +13,9 @@ #undef KeyRelease #else #include // isatty +#ifndef __APPLE__ #include // XInitThreads +#endif #undef KeyPress #undef KeyRelease From ab68d6fc27bcf3a25e06684506b17fb4f9d3bae0 Mon Sep 17 00:00:00 2001 From: bruno-dasilva <8520801+bruno-dasilva@users.noreply.github.com> Date: Mon, 29 Jun 2026 03:26:11 -0700 Subject: [PATCH 15/39] fix: address sse2neon/streflop macro redefinition warning (#2999) --- rts/System/simd_compat.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rts/System/simd_compat.h b/rts/System/simd_compat.h index a405e02f587..c68b59e3484 100644 --- a/rts/System/simd_compat.h +++ b/rts/System/simd_compat.h @@ -3,6 +3,19 @@ #ifdef SSE2NEON #include "lib/sse2neon/sse2neon.h" + // sse2neon leaks 's FE_XXX macros, which collide with the ones streflop + // redefines and trigger a #warning. Undef them here so streflop gets a clean slate. + #undef FE_INVALID + #undef FE_DENORMAL + #undef FE_DIVBYZERO + #undef FE_OVERFLOW + #undef FE_UNDERFLOW + #undef FE_INEXACT + #undef FE_ALL_EXCEPT + #undef FE_TONEAREST + #undef FE_DOWNWARD + #undef FE_UPWARD + #undef FE_TOWARDZERO #else #ifdef _MSC_VER #include // MSVC umbrella From 52b55cf38ac65a4b30257ccc8d0815693da39cac Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Mon, 29 Jun 2026 11:34:36 +0100 Subject: [PATCH 16/39] glad: do not build the GLX loader on macOS glad_glx.c includes X11/X.h to provide the GLX windowing-system bindings, but macOS has no X11/GLX. The Glad CMakeLists added glad_glx.c for every UNIX-and-not-MinGW platform, so building any GL-enabled target (e.g. engine-legacy) on macOS failed: fatal error: X11/X.h: No such file or directory macOS resolves GL entry points without GLX (the engine's glxHandler is already #ifdef'd out on __APPLE__), so exclude glad_glx.c on Apple and build only the core glad.c there. --- rts/lib/glad/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rts/lib/glad/CMakeLists.txt b/rts/lib/glad/CMakeLists.txt index a67e710dd36..7f7391dd7d3 100644 --- a/rts/lib/glad/CMakeLists.txt +++ b/rts/lib/glad/CMakeLists.txt @@ -1,10 +1,10 @@ cmake_minimum_required(VERSION 3.5) project(Glad) -if (UNIX AND NOT MINGW) +if (UNIX AND NOT MINGW AND NOT APPLE) add_library(glad glad.c glad_glx.c) -else (UNIX AND NOT MINGW) +else (UNIX AND NOT MINGW AND NOT APPLE) add_library(glad glad.c) -endif (UNIX AND NOT MINGW) +endif (UNIX AND NOT MINGW AND NOT APPLE) target_include_directories(glad PUBLIC /) \ No newline at end of file From 4034d5a13273ae1078c3aa8b59c525bd6f51c052 Mon Sep 17 00:00:00 2001 From: bruno-dasilva <8520801+bruno-dasilva@users.noreply.github.com> Date: Mon, 29 Jun 2026 05:41:52 -0700 Subject: [PATCH 17/39] docs: add ENGINE_PERFORMANCE.md with some notes on engine internals (#2919) Co-Authored-By: Claude Opus 4.6 (1M context) --- AGENTS.md | 4 +++ coding-agents/ENGINE_PERFORMANCE.md | 55 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 coding-agents/ENGINE_PERFORMANCE.md diff --git a/AGENTS.md b/AGENTS.md index 0f87ef9915c..63780f55c46 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -388,6 +388,10 @@ The engine uses custom thread pools. See `THREADPOOL` define and related code. 4. Follow the workflow in `contributing.md` 5. Disclose any AI assistance used +### Additional docs +Please see @coding-agents/ for additional documentation: +- coding-agents/ENGINE_PERFORMANCE.md — notes on scale targets and engine performance internals. Useful for performance related changes. + ## Additional Resources - Official website: https://recoilengine.org diff --git a/coding-agents/ENGINE_PERFORMANCE.md b/coding-agents/ENGINE_PERFORMANCE.md new file mode 100644 index 00000000000..f3bb67f5d77 --- /dev/null +++ b/coding-agents/ENGINE_PERFORMANCE.md @@ -0,0 +1,55 @@ +# Engine performance + +Recoil is an RTS engine built for large-scale games — designed to handle thousands of units at once. + +## Scale target + +- **Target:** ~10k concurrent units, *including buildings*. +- Mobile units tend to be ~40% of that late-game total. +- Largest seen in a real game: ~17.7k units. That's a data point, not a design target. + +## Sim, draw, and update frames + +The main loop is `Update → Draw`, repeating — see the diagram and table below for the per-phase breakdown. Each iteration first drains any queued sim-frame packets (0..N per iteration), then renders one draw frame. `CGame::Update` dispatches `SimFrame()` calls as `NETMSG_NEWFRAME` packets arrive; `CGame::Draw` runs the unsynced update phase and then renders. The sim burst is capped at ~500 ms (`minDrawFPS`) so draw always gets to run, and it's all one thread — sim and rendering are **not concurrent**; parallelism only happens *inside* a phase. + +Conversely, if no sim frames are in the queue the main loop runs `Draw`/`UpdateUnsynced` as fast as possible — many draw iterations can pass between successive sim frames, with visuals interpolating smoothly in between via `globalRendering->timeOffset`. + +``` +main-loop iteration (repeats as fast as possible) +├── CGame::Update (mostly synced) +│ └── SimFrame × 0..N ← processes queued sim frames capped at +| ~500ms per iteration +└── CGame::Draw (unsynced) + ├── UpdateUnsynced ← unsynced update phase + └── render world + screen ← Draw::World + Draw::Screen +``` + +| Phase | Rate | Synced? | Responsibility | +|---|---|---|---| +| **Sim frame** — `CGame::SimFrame` | fixed 30 Hz (`GAME_SPEED`) | mostly yes | advance deterministic state: units, pathing, projectiles, line-of-sight, scripts, Lua `GameFrame` | +| **Draw frame** — `CGame::Draw` | variable | no | update phase (see below) + render world/screen | +| **Update phase** — `CGame::UpdateUnsynced` *(inside draw frame)* | per draw frame | no | timings, interpolation, camera, GUI, sound, world-drawer prep | + +### Profiler buckets + +The engine `CTimeProfiler` (and the `benchmark` tool) report three peer buckets: `Sim` (the whole synced step), `Update` (`CGame::UpdateUnsynced`), and `Draw` (rendering only, *excluding* the Update that runs first). + +`Sim` is **"mostly" synced**: it also bills unsynced work that runs inline during `SimFrame`. +- **Explicit Lua callins** — `GameFrame`/`GameFramePost` run near the start of each sim frame. +- **Event-driven Lua callins** — unsynced widgets can subscribe to synced game events, so their handlers run inline as those events fire during the frame. +- **C++-only unsynced sections** — e.g. the MT projectile visual pass (`Sim::Projectiles::UpdateUnsyncedMT`) and ghosted-building updates (`CUnitDrawer::UpdateGhostedBuildings`). + +### Scheduling and CPU budget + +- Sim has a target rate set by the server; draw is as fast as the hardware allows. The sim target is `30 Hz × speedFactor`; at a speed factor of 1x, in-game time tracks real-world time 1:1, and at 2x speed the server fires twice as many sim frames per real-world second so the world evolves twice as fast. +- **Zero, one, or many** sim frames per draw frame — if the client falls behind, pending sim frames burst in the next iteration to catch up. +- Visuals interpolate between sim frames, so draw rate can exceed sim rate without stutter. +- Sim time is carefully budgeted and scheduled against draw frames (because they run serially) so there's always a minimum fps for the player + +## Multi-threading + +The engine runs one **main thread** plus a pool of **worker threads**, all pinned to distinct cores. We typically aim for 6-8 worker threads. The main thread drives the sim/draw loop; workers pick up parallel work dispatched from the main thread (via `for_mt` and friends in `rts/System/Threading/ThreadPool.h`). The main thread also participates in draining the task queue while it waits. + +Most parallel work in the engine is **homogeneous** — the same operation applied over many items (unit updates, projectile steps, etc.) via `for_mt`. Keeping parallel work homogeneous is a deliberate discipline: it makes determinism easier to reason about and keeps sim output independent of how work happens to land across threads. + +**QTPFS is the one heterogeneous exception.** The quad-tree pathfinder maintains its own per-worker search state (`SearchThreadData`, `SparseData`) independent of engine sim state, which lets it safely run path searches on the worker pool *in the background* via `for_mt_background`. Background tasks yield to higher-priority work by rescheduling themselves when other jobs arrive, so QTPFS soaks up idle worker capacity without preempting foreground parallelism. From 7e07543f7fd14c11ac8df2ef0e4785e081a07aef Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Mon, 29 Jun 2026 13:58:30 +0100 Subject: [PATCH 18/39] System: add missing includes for libc++ These three headers use std:: algorithms but rely on being pulled in transitively. libstdc++ does so; libc++ (clang/macOS) does not, so they can fail to compile under libc++ depending on the version. Symbols that require the include: - rts/System/Matrix44f.h -> std::copy - rts/System/SpringHashMap.hpp -> std::fill_n - rts/System/SpringHashSet.hpp -> std::fill_n Adding the include is "include what you use" correctness and is a no-op on toolchains that already provide it transitively. No functional change. Cherry-picked from ExaDev/RecoilEngine (0ed29d550e, 0b4bd10a1e, 3adabd005a). AI assistance: changes identified and applied with Claude (Anthropic); verified by a human (compiled under clang/libc++, no regression). --- rts/System/Matrix44f.h | 1 + rts/System/SpringHashMap.hpp | 1 + rts/System/SpringHashSet.hpp | 1 + 3 files changed, 3 insertions(+) diff --git a/rts/System/Matrix44f.h b/rts/System/Matrix44f.h index 3dd6767dd33..bd5e6d1fc5b 100644 --- a/rts/System/Matrix44f.h +++ b/rts/System/Matrix44f.h @@ -2,6 +2,7 @@ #pragma once +#include #include #include #include diff --git a/rts/System/SpringHashMap.hpp b/rts/System/SpringHashMap.hpp index 1bf487a6111..f6a359cb14d 100644 --- a/rts/System/SpringHashMap.hpp +++ b/rts/System/SpringHashMap.hpp @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include diff --git a/rts/System/SpringHashSet.hpp b/rts/System/SpringHashSet.hpp index 3375e12520c..366a5d35211 100644 --- a/rts/System/SpringHashSet.hpp +++ b/rts/System/SpringHashSet.hpp @@ -6,6 +6,7 @@ #pragma once +#include #include #include // malloc #include From cf3b083040550e9cb6047839624fea829f3ca0c0 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Mon, 29 Jun 2026 10:40:36 +0100 Subject: [PATCH 19/39] DemoTool: link nowide and fmt for their include directories DemoTool compiles engine FileSystem sources (FileHandler, FileSystem) that include nowide/fstream.hpp and fmt/printf.h, but its target_link_libraries omitted the nowide and fmt targets. The engine builds obtain those include directories transitively through the nowide::nowide and fmt::fmt INTERFACE targets; DemoTool linked neither, so the build failed when the headers were not on a default search path (observed building the demotool target on macOS). Link nowide::nowide and fmt::fmt so their INTERFACE include directories propagate to the demotool target. Co-authored-by: Robert Burnham --- tools/DemoTool/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/DemoTool/CMakeLists.txt b/tools/DemoTool/CMakeLists.txt index dc9cbf66ecf..fdc47e0461d 100644 --- a/tools/DemoTool/CMakeLists.txt +++ b/tools/DemoTool/CMakeLists.txt @@ -70,6 +70,8 @@ target_link_libraries(demotool gflags_nothreads_static 7zip ${ZLIB_LIBRARY} + nowide::nowide + fmt::fmt ${PLATFORM_LIBS} Tracy::TracyClient ) From f898efc6c31b3871cc8f60ed2c567a5de41db696 Mon Sep 17 00:00:00 2001 From: Robert Burnham Date: Thu, 18 Jun 2026 23:30:55 -0500 Subject: [PATCH 20/39] Fix truncated sync checksum in demotool dump --- tools/DemoTool/DemoTool.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/DemoTool/DemoTool.cpp b/tools/DemoTool/DemoTool.cpp index 96bb8930344..d26b0c9b955 100644 --- a/tools/DemoTool/DemoTool.cpp +++ b/tools/DemoTool/DemoTool.cpp @@ -1,5 +1,6 @@ /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ +#include #include #include #include @@ -417,7 +418,7 @@ void TrafficDump(CDemoReader& reader, bool trafficStats) //uchar myPlayerNum; int frameNum; uint checksum; std::cout << "NETMSG_SYNCRESPONSE: Playernum: "<< (unsigned)buffer[1]; std::cout << " Framenum: " << *(int*)(buffer+2); - std::cout << " Checksum: " << (unsigned)buffer[6]; + std::cout << " Checksum: " << *(uint32_t*)(buffer+6); std::cout << std::endl; break; case NETMSG_DIRECT_CONTROL: From 8141bf23dc42630bb235c155ca7db8ae56ba7610 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Tue, 30 Jun 2026 10:53:26 +0100 Subject: [PATCH 21/39] Portability in archive handlers * avoid parenthesised aggregate init and name the type explicitly (not supported everywhere) * avoid narrowing conversions. --- rts/System/FileSystem/Archives/DirArchive.cpp | 2 +- rts/System/FileSystem/Archives/SevenZipArchive.cpp | 8 ++++---- rts/System/FileSystem/Archives/ZipArchive.cpp | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/rts/System/FileSystem/Archives/DirArchive.cpp b/rts/System/FileSystem/Archives/DirArchive.cpp index 829a56c4251..55242d39db0 100644 --- a/rts/System/FileSystem/Archives/DirArchive.cpp +++ b/rts/System/FileSystem/Archives/DirArchive.cpp @@ -42,7 +42,7 @@ CDirArchive::CDirArchive(const std::string& archiveName) // all variables here will use forward slashes, no need for conversion std::string rawFileName = dataDirsAccess.LocateFile(dirName + origName); - files.emplace_back(origName, std::move(rawFileName), -1, 0); + files.emplace_back(Files{origName, std::move(rawFileName), -1, 0}); // convert to lowercase and store lcNameIndex[StringToLower(std::move(origName))] = static_cast(files.size() - 1); diff --git a/rts/System/FileSystem/Archives/SevenZipArchive.cpp b/rts/System/FileSystem/Archives/SevenZipArchive.cpp index e40ce9e798d..d5994770067 100644 --- a/rts/System/FileSystem/Archives/SevenZipArchive.cpp +++ b/rts/System/FileSystem/Archives/SevenZipArchive.cpp @@ -147,12 +147,12 @@ CSevenZipArchive::CSevenZipArchive(const std::string& name) continue; } - const auto& fd = fileEntries.emplace_back( - i, //fp - SzArEx_GetFileSize(&db, i), // size + const auto& fd = fileEntries.emplace_back(FileEntry{ + static_cast(i), //fp + static_cast(SzArEx_GetFileSize(&db, i)), // size db.MTime.Vals ? static_cast(CTimeUtil::NTFSTimeToTime64(db.MTime.Vals[i].Low, db.MTime.Vals[i].High)) : 0, // modtime std::move(fileName.value()) // origName - ); + }); lcNameIndex.emplace(StringToLower(fd.origName), fileEntries.size() - 1); } diff --git a/rts/System/FileSystem/Archives/ZipArchive.cpp b/rts/System/FileSystem/Archives/ZipArchive.cpp index 37914269c49..e7b13e3e790 100644 --- a/rts/System/FileSystem/Archives/ZipArchive.cpp +++ b/rts/System/FileSystem/Archives/ZipArchive.cpp @@ -58,13 +58,13 @@ CZipArchive::CZipArchive(const std::string& archiveName) unz_file_pos fp{}; unzGetFilePos(zip, &fp); - const auto& fd = fileEntries.emplace_back( + const auto& fd = fileEntries.emplace_back(FileEntry{ std::move(fp), //fp - info.uncompressed_size, //size + static_cast(info.uncompressed_size), //size fName, //origName - info.crc, //crc + static_cast(info.crc), //crc static_cast(CTimeUtil::DosTimeToTime64(info.dosDate)) //modTime - ); + }); lcNameIndex.emplace(StringToLower(fd.origName), fileEntries.size() - 1); } From 898dd6cf869d7c590f7f1bd906fdd40e92f414e3 Mon Sep 17 00:00:00 2001 From: Bruno Da Silva Date: Sat, 11 Apr 2026 17:23:07 +0000 Subject: [PATCH 22/39] docs: add BACKWARDS_COMPATIBILITY.md guidance doc for coding agents --- AGENTS.md | 2 +- coding-agents/BACKWARDS_COMPATIBILITY.md | 25 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 coding-agents/BACKWARDS_COMPATIBILITY.md diff --git a/AGENTS.md b/AGENTS.md index 63780f55c46..f62bffeb42e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -391,7 +391,7 @@ The engine uses custom thread pools. See `THREADPOOL` define and related code. ### Additional docs Please see @coding-agents/ for additional documentation: - coding-agents/ENGINE_PERFORMANCE.md — notes on scale targets and engine performance internals. Useful for performance related changes. - +- coding-agents/BACKWARDS_COMPATIBILITY.md - notes on when we should strive to be backwards compatible. Reference it for any major reworks or api changes. ## Additional Resources - Official website: https://recoilengine.org diff --git a/coding-agents/BACKWARDS_COMPATIBILITY.md b/coding-agents/BACKWARDS_COMPATIBILITY.md new file mode 100644 index 00000000000..d36c3775a78 --- /dev/null +++ b/coding-agents/BACKWARDS_COMPATIBILITY.md @@ -0,0 +1,25 @@ +# Backwards compatibility + +Recoil isn't 100% beholden to backwards compatibility, but breaking changes are weighed carefully against their benefit. Backwards compatiblity is a constraint, not a veto. + +## Why the bar is high + +Recoil games aren't short-cycle Unreal/Unity titles — they're lifetime hobby projects. There is no steady stream of new games picking up the latest engine; the games we have are the games we have. They fall into two camps, and neither absorbs churn well: + +- **Mature games** need stability above all else. +- **Games still in active development** have the flexibility, but rarely the volunteer bandwidth to chase significant engine breakage. + +## How to weigh a change + +- Quantify the benefit (perf, correctness, maintainability) concretely, not in the abstract. +- Identify which games or content would break, and how mechanical the fix is on their side. "Rename a call site" is very different from "rearchitect your gadget." +- Prefer changes whose blast radius is contained or whose adaptation is mechanical. Avoid changes that force games to rethink core logic with no real mitigation path. + +## Precedents + +- **Multi-threaded unit movement & collision** — landed with a large perf win and effectively no game-side impact (ignoring incidentally-fixed bugs). This is the shape of change to look for. +- **Multi-threading `Unit::Update`, `Unit::SlowUpdate`, or projectiles** — don't. The impact on games would be huge and there isn't much that can be done to mitigate it. Not a path worth proposing. + +## The upshot + +Backwards compatiblity constraints don't close the door on performance work — they just point it at the areas where the blast radius is small. Plenty of wins are still on the table; pick the ones games don't have to pay for. From 487fd436e756bdaa5ad4469adbef3f9638bf3101 Mon Sep 17 00:00:00 2001 From: bruno-dasilva <8520801+bruno-dasilva@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:21:24 -0700 Subject: [PATCH 23/39] docs: improve running/testing instructions in AGENTS.md (#2917) --- AGENTS.md | 160 ++++++++++++++++++++++++++------------- docker-build-v2/build.sh | 11 ++- test/AGENTS.md | 128 +++++++++++++++++++++++++++++++ 3 files changed, 245 insertions(+), 54 deletions(-) create mode 100644 test/AGENTS.md diff --git a/AGENTS.md b/AGENTS.md index f62bffeb42e..4d346e24500 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,18 +8,49 @@ RecoilEngine is an open-source real-time strategy (RTS) game engine written in C ## Build Commands +### Submodules + +The repo uses git submodules for vendored libraries (`rts/lib/*`, `tools/pr-downloader`, AI skirmish bots, etc.). If you cloned without `--recurse-submodules`, initialize them before building: +```bash +git submodule update --init --recursive +``` + ### Building the Engine **Using Docker (Recommended):** ```bash -# Build for Linux +# Full build (default: RELWITHDEBINFO, -O3 -g -DNDEBUG, Ninja) +# Output lands in build--/ (e.g. build-amd64-linux/) and the +# ready-to-use install in build-amd64-linux/install/ docker-build-v2/build.sh linux -# Build for Windows +# Parallelism +docker-build-v2/build.sh -j 8 linux + +# Windows cross-build docker-build-v2/build.sh windows -# Build with custom CMake options +# Change optimization level — trailing -D… is forwarded to configure.sh and +# overrides the baked-in RELWITHDEBINFO default. docker-build-v2/build.sh linux -DCMAKE_BUILD_TYPE=DEBUG +docker-build-v2/build.sh linux -DCMAKE_BUILD_TYPE=RELEASE +docker-build-v2/build.sh linux -DCMAKE_BUILD_TYPE=PROFILE + +# Combine cmake options (configure phase) +docker-build-v2/build.sh linux -DBUILD_spring-headless=OFF -DTRACY_ENABLE=ON + +# List all available cmake options and their current values +docker-build-v2/build.sh --configure linux -LH + +# Build a specific target — use --compile so args flow to `cmake --build`, +# not to configure. Without --compile, `-t …` would be rejected by configure. +docker-build-v2/build.sh --compile linux -t engine-headless +docker-build-v2/build.sh --compile linux -t engine-legacy +docker-build-v2/build.sh --compile linux -t tests --verbose + +# Split the phases +docker-build-v2/build.sh --configure linux # configure only +docker-build-v2/build.sh --compile linux # compile only (reuses existing config) ``` **Without Docker:** @@ -27,16 +58,44 @@ docker-build-v2/build.sh linux -DCMAKE_BUILD_TYPE=DEBUG # Create build directory mkdir -p build && cd build -# Configure +# Configure — project requires C++23 (clang ≥ 17 or gcc ≥ 13 on PATH). +# CMAKE_BUILD_TYPE defaults to RELWITHDEBINFO when omitted. cmake .. -# Build specific target -cmake --build . --target engine-headless -j$(nproc) - -# Build all -cmake --build . -j$(nproc) +# Optional: Default generator is Unix Makefiles; add `-G Ninja` for faster builds if ninja is installed. +cmake -G Ninja .. + +# Optional: pin to gcc-13 + gold linker via the in-repo toolchain file +# (tracked under docker-build-v2/; same compiler the docker build uses). +cmake \ + -DCMAKE_TOOLCHAIN_FILE=../docker-build-v2/images/all-linux/toolchain.cmake .. + +# Optional: speed up incremental builds with ccache +cmake \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache .. + +# Change optimization level by re-running cmake (no wipe required): +cmake -DCMAKE_BUILD_TYPE=DEBUG .. # no optimization, full symbols +cmake -DCMAKE_BUILD_TYPE=RELEASE .. # optimized, no debug info +cmake -DCMAKE_BUILD_TYPE=RELWITHDEBINFO .. # optimized + debug info (default) +cmake -DCMAKE_BUILD_TYPE=PROFILE .. # optimized + profiling hooks + +# Build (generator-agnostic — works under Ninja or Make) +cmake --build . + +# Build a specific target +cmake --build . --target engine-headless +cmake --build . --target engine-legacy +cmake --build . --target engine-dedicated +cmake --build . --target tests ``` +> The docker flow writes to **`build--/`** (e.g. `build-amd64-linux/` +> or `build-amd64-windows/`), which is a different directory than the `build/` +> used by this flow. When running tests, point commands at whichever build +> directory you populated. + ### Build Types - `DEBUG` - Debug build with full symbols and no optimization - `RELEASE` - Optimized release build @@ -44,55 +103,64 @@ cmake --build . -j$(nproc) - `PROFILE` - Profiling build ### Build Targets -- `engine-legacy` - Main engine build -- `engine-headless` - Headless server build -- `engine-dedicated` - Dedicated server build -- `tests` - Build all test executables -- `check` - Build and run all tests -- `spring-content` - Build game content packages +- `engine-legacy` — main interactive engine build +- `engine-headless` — headless engine (no graphics) +- `engine-dedicated` — dedicated server +- `unitsync` — unitsync shared library +- `pr-downloader` — content downloader tool +- `tests` — phony; builds every `test_*` executable under `build/test/` +- `check` — phony; depends on `engine-headless` + all `test_*` executables, then runs ctest with `--output-on-failure -V` +- `install` — install into `CMAKE_INSTALL_PREFIX` ## Testing -### Test Framework -The project uses **Catch2** for unit testing. Test files are located in the `test/` directory. +### Writing Tests +See `test/AGENTS.md` for details on writing tests, available compile flags, patterns, and test helpers. ### Running Tests **Build and run all tests:** ```bash -# From build directory -make tests # Build all test executables -make check # Build and run all tests via CTest -make test # Alternative: run via CTest +# From build/ — ctest / check recipes below assume a non-docker build. +# For a docker build, run `docker-build-v2/build.sh --compile linux -t check` +# (runs ctest inside the container) or invoke the binaries in +# build-amd64-linux/test/ directly. +cmake --build . --target tests # build all test executables (no run) +ctest # run all tests (does not rebuild) +# OR +cmake --build . --target check # rebuild engine-headless + all tests, then run ctest -V ``` -**Run a single test:** +`check` is the safe default when iterating; bare `ctest` is faster when nothing relevant has changed since the last build. + +**Run a single test (from repo root):** ```bash -# Tests are built as executable binaries in the build directory -# Pattern: test_ +# Tests are built as executable binaries under /test/ +# Pattern: /test/test_ +# where depends on if you built in docker or not (see above). -# Run specific test executable -./test_Float3 -./test_Matrix44f -./test_SyncedPrimitive -./test_UDPListener +./build/test/test_Float3 +./build/test/test_Matrix44f +./build/test/test_SyncedPrimitive +./build/test/test_UDPListener -# Run with verbose output -./test_Float3 -s +# Catch2: show passing assertions too +./build/test/test_Float3 -s -# Run specific test case -./test_Float3 "TestSection" +# Run a specific test case by name (positional arg matches TEST_CASE name, supports wildcards) +./build/test/test_Float3 "Float3" +./build/test/test_Float3 "Float34_*" ``` -**Run via CTest:** +**Run via CTest (from inside build/):** ```bash -# Run specific test by name -ctest -R Float3 -V +# Filter by regex, show output only on failure +ctest -R Float3 --output-on-failure -# Run with regex pattern -ctest -R Matrix -V +# Same, but verbose (full stdout regardless of result) +ctest -R Float3 -V -# List all available tests +# List all registered tests without running ctest -N ``` @@ -325,20 +393,6 @@ Use preprocessor directives for platform-specific code: - Use tabs for indentation in CMake files - Keep lines reasonably short -### Adding Tests -In `test/CMakeLists.txt`: -```cmake -set(test_name TestName) -set(test_src - "${CMAKE_CURRENT_SOURCE_DIR}/path/to/TestFile.cpp" - ${test_Common_sources} -) -set(test_libs - library_name -) -add_spring_test(${test_name} "${test_src}" "${test_libs}" "${test_flags}") -``` - ## Project Structure - `rts/` - Main engine source code diff --git a/docker-build-v2/build.sh b/docker-build-v2/build.sh index 765ba4a1495..6bded8b8b57 100755 --- a/docker-build-v2/build.sh +++ b/docker-build-v2/build.sh @@ -179,7 +179,16 @@ if [[ "$GIT_DIR" != "$GIT_COMMON_DIR" ]]; then WORKTREE_MOUNTS="-v $GIT_COMMON_DIR:$GIT_COMMON_DIR:ro" fi -$RUNTIME run --platform=linux/$ARCH -it --rm \ +# Docker's -t requires stdin AND stdout to be TTYs; in CI, pipes, or agent +# contexts one or both are missing and docker errors out with "the input +# device is not a TTY". Only add -t when it's safe; -i is harmless either +# way (non-interactive stdin just sees EOF). +TTY_FLAG= +if [[ -t 0 && -t 1 ]]; then + TTY_FLAG=-t +fi + +$RUNTIME run --platform=linux/$ARCH -i $TTY_FLAG --rm \ -v "$CWD${P}":/build/src:z,ro \ -v "$CWD${P}.cache${P}ccache-$PLATFORM":/build/cache:z,rw \ -v "$CWD${P}build-$PLATFORM":/build/out:z,rw \ diff --git a/test/AGENTS.md b/test/AGENTS.md new file mode 100644 index 00000000000..17fe17159f0 --- /dev/null +++ b/test/AGENTS.md @@ -0,0 +1,128 @@ +# AGENTS.md + +This file provides guidance to coding agents when working with code in this repository's test folder. + +## Build & Run Tests + +```bash +# From build/: build all test executables +cmake --build . --target tests + +# From build/: run tests. ctest/check recipes here assume a non-docker +# build. For a docker build, run `docker-build-v2/build.sh --compile linux -t check` +# (runs ctest inside the container) or invoke binaries in +# build-amd64-linux/test/ directly (see below). +ctest # run already-built tests; does not rebuild +cmake --build . --target check # rebuild engine-headless + all tests first, then ctest -V + +# From build/: run ctest with filters +ctest --output-on-failure # concise; only shows failing output +ctest -R Float3 --output-on-failure # filter by name regex +ctest -R Float3 -V # same, verbose +ctest -N # list all registered tests without running + +# From repo root: run a single test binary directly (fastest iteration). +# Use build-amd64-linux/ instead of build/ if you built via docker. +./build/test/test_Float3 +./build/test/test_Float3 -s # Catch2: show passing assertions too +./build/test/test_Float3 "Float3" # filter by TEST_CASE name (supports wildcards) +``` + +`cmake --build . --target check` is the full-fat target: it depends on `engine-headless` and +every `test_*` executable, so it relinks anything stale before running ctest with +`--output-on-failure -V`. Use bare `ctest` when you want to skip the rebuild. + +## Framework + +**Catch2** (amalgamated single-header version) in `lib/catch2/`. Custom main in `lib/catch2/catch_main.cpp` with leak detection enabled via `CATCH_AMALGAMATED_CUSTOM_MAIN`. + +## Test Organization + +``` +engine/System/ # Core system tests (math, threading, I/O, serialization) +engine/Sim/Misc/ # Simulation tests (QuadField, Ellipsoid) +lib/luasocket/ # Lua socket restriction tests +other/ # Mutex benchmarks, memory pool tests +unitsync/ # UnitSync API tests +validation/ # Integration tests (shell scripts that run full game simulation) +tools/CompileFailTest/ # Negative test framework (tests that must NOT compile) +headercheck/ # Header isolation tests (cmake -DHEADERCHECK=ON) +``` + +## Adding a New Test + +1. Create test source in the appropriate subdirectory under `engine/`, `other/`, etc. +2. In `test/CMakeLists.txt`, add a block using the `add_spring_test` macro: +```cmake +set(test_name MyTest) +set(test_src + "${CMAKE_CURRENT_SOURCE_DIR}/engine/System/testMyTest.cpp" + ${test_Common_sources} +) +set(test_libs "") +set(test_flags "-DNOT_USING_CREG -DNOT_USING_STREFLOP -DBUILDING_AI") +add_spring_test(${test_name} "${test_src}" "${test_libs}" "${test_flags}") +``` +3. The macro creates executable `test_` and registers it with ctest as `test`. + +## Common Compile Flags + +| Flag | Purpose | +|------|---------| +| `-DUNIT_TEST` | Always set for all tests (global) | +| `-DSYNCCHECK` | Always set for all tests (global) | +| `-DNOT_USING_CREG` | Stubs out `CR_*` macros. Default unless the test exercises save/load serialization. | +| `-DNOT_USING_STREFLOP` | Falls back to ``. Default unless the test verifies synced floating-point determinism. | +| `-DBUILDING_AI` | Makes engine headers skip engine-only paths. Pair with `NOT_USING_CREG` and `NOT_USING_STREFLOP`. | +| `-DTHREADPOOL` | Selects the real thread pool over the stub. Set only when the test needs real parallelism. | +| `-DUNITSYNC` | Marks the file as part of unitsync. Only needed for tests that link the unitsync library. | + +## Patterns + +### Basic test file +```cpp +#include +#include "System/Log/ILog.h" + +TEST_CASE("MyFeature") { + CHECK(1 + 1 == 2); + SECTION("sub-case") { + CHECK(true); + } +} +``` + +### Tests that need timing +```cpp +#include "System/Misc/SpringTime.h" +TEST_CASE("TimingTest") { + InitSpringTime ist; // RAII - must be instantiated before using spring_time + // ... +} +``` + +### Thread-safe assertions +Catch2 is NOT thread-safe. Multi-threaded tests must guard assertions: +```cpp +static spring::mutex m; +#define SAFE_CHECK(expr) { std::lock_guard lk(m); CHECK(expr); } +``` + +### Compile-fail tests +Tests that verify code correctly fails to compile. Source uses `#ifdef FAIL` guards: +```cpp +#ifdef FAIL +#ifdef TEST1 + int x = someStronglyTypedEnum; // must not compile +#endif +#endif +``` +Registered in CMakeLists.txt via: +```cmake +spring_test_compile_fail(testName_fail1 ${test_src} "-DTEST1") +``` + +## Test Helpers (mock/stub files) + +- `engine/System/NullGlobalConfig.cpp` — provides default `globalConfig` without full engine init +- `engine/System/Nullerrorhandler.cpp` — stubs `ErrorMessageBox()` to prevent GUI popups From d313449ce9fc71b65280285651162f4061fdf171 Mon Sep 17 00:00:00 2001 From: sprunk Date: Thu, 2 Jul 2026 10:47:46 +0200 Subject: [PATCH 24/39] Move `isHeadless` from `Engine` to `Platform`. (#3067) --- doc/site/content/changelogs/_index.markdown | 2 +- rts/Lua/LuaConstEngine.cpp | 3 --- rts/Lua/LuaConstPlatform.cpp | 4 ++++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/site/content/changelogs/_index.markdown b/doc/site/content/changelogs/_index.markdown index 55096582c3a..0e073538cb7 100644 --- a/doc/site/content/changelogs/_index.markdown +++ b/doc/site/content/changelogs/_index.markdown @@ -74,7 +74,7 @@ This is the bleeding-edge changelog since version 2025.06, for **pre-release 202 - add `Spring.GetClosestEnemyUnit(x, y, z, range = inf, allyTeamID, bool useLoS = true, bool spherical = false, bool requireEnemyToSeePos = false) → unitID?` to LuaRules. - large QTPFS perf improvements. - always output logs to stdout. -- add `Engine.isHeadless`, available in unsynced only. +- add boolean `Platform.isHeadless`. - archive cache version 20 → 21. ## Fixes diff --git a/rts/Lua/LuaConstEngine.cpp b/rts/Lua/LuaConstEngine.cpp index 068a26d0ecf..0d38c1ffbf0 100644 --- a/rts/Lua/LuaConstEngine.cpp +++ b/rts/Lua/LuaConstEngine.cpp @@ -59,9 +59,6 @@ bool LuaConstEngine::PushEntries(lua_State* L) LuaPushNamedString(L, "buildFlags" , SpringVersion::GetAdditional()); LuaPushNamedNumber(L, "wordSize", (!CLuaHandle::GetHandleSynced(L))? Platform::NativeWordSize() * 8: 0); - if (!CLuaHandle::GetHandleSynced(L)) - LuaPushNamedBool(L, "isHeadless", SpringVersion::IsHeadless()); - LuaPushNamedNumber(L, "gameSpeed", GAME_SPEED); LuaPushNamedNumber(L, "maxCustomPaletteID", MAX_CUSTOM_COLORS - 1); diff --git a/rts/Lua/LuaConstPlatform.cpp b/rts/Lua/LuaConstPlatform.cpp index 0a5f667ed80..7262bd7d5a5 100644 --- a/rts/Lua/LuaConstPlatform.cpp +++ b/rts/Lua/LuaConstPlatform.cpp @@ -2,6 +2,7 @@ #include "LuaConstPlatform.h" #include "LuaUtils.h" +#include "Game/GameVersion.h" #include "System/Platform/Hardware.h" #include "System/Platform/Misc.h" #include "Rendering/GlobalRendering.h" @@ -147,5 +148,8 @@ bool LuaConstPlatform::PushEntries(lua_State* L) /*** @field Platform.macAddrHash string */ LuaPushNamedString(L, "macAddrHash", Platform::GetMacAddrHash()); + /*** @field Platform.isHeadless boolean Is this a headless build which only simulates and doesnt offer interactive IO? */ + LuaPushNamedBool(L, "isHeadless", SpringVersion::IsHeadless()); + return true; } From 092cb0a04ef4db8230bf781fbdc2a01d74a0c6a3 Mon Sep 17 00:00:00 2001 From: Alexander Heistermann Date: Thu, 2 Jul 2026 18:04:17 -0500 Subject: [PATCH 25/39] Add Lua trace ray functions (#1624) * adds `Spring.TraceRayBetweenPositions(xA, yA, zA, xB, yB, zB, type)` * adds `Spring.TraceRayInDirection(x, y, z, dx, dy, dz, length, type)` * type is a string, "unit", "feature", or "both" * both return an array of `{distance, objID, objType}` sorted by increasing distance --- rts/Lua/LuaSyncedRead.cpp | 138 ++++++++++++++++++++++++++++++++++++++ rts/Lua/LuaSyncedRead.h | 5 +- 2 files changed, 140 insertions(+), 3 deletions(-) diff --git a/rts/Lua/LuaSyncedRead.cpp b/rts/Lua/LuaSyncedRead.cpp index 40b25741b52..3983a71f3ff 100644 --- a/rts/Lua/LuaSyncedRead.cpp +++ b/rts/Lua/LuaSyncedRead.cpp @@ -399,6 +399,8 @@ bool LuaSyncedRead::PushEntries(lua_State* L) REGISTER_LUA_CFUNC(TraceRayGroundInDirection); REGISTER_LUA_CFUNC(TraceRayGroundBetweenPositions); + REGISTER_LUA_CFUNC(TraceRayInDirection); + REGISTER_LUA_CFUNC(TraceRayBetweenPositions); REGISTER_LUA_CFUNC(GetRadarErrorParams); @@ -9011,6 +9013,142 @@ int LuaSyncedRead::GetUnitScriptNames(lua_State* L) return 1; } + +static int TraceRayImpl(lua_State *const L, const float3 &pos, const float3 &dir, const float maxLen, std::string_view type) +{ + if (type != "unit" && type != "feature" && type != "both") + return luaL_error(L, "invalid type '%s', expected 'unit', 'feature', or 'both'", type.data()); + + const bool testUnits = (type == "unit" || type == "both"); + const bool testFeatures = (type == "feature" || type == "both"); + + QuadFieldQuery qfQuery; + quadField.GetQuadsOnRay(qfQuery, pos, dir, maxLen); + + spring::unordered_set testedUnitIDs; + spring::unordered_set testedFeatureIDs; + std::vector > hits; + + for (const int quadIdx : *qfQuery.quads) { + const CQuadField::Quad& quad = quadField.GetQuad(quadIdx); + + if (testUnits) { + for (const auto *unit : quad.units) { + if (!unit->HasCollidableStateBit(CSolidObject::CSTATE_BIT_QUADMAPRAYS)) + continue; + + if (!testedUnitIDs.insert(unit->id).second) + continue; + + if (!LuaUtils::IsUnitInLos(L, unit)) + continue; + + CollisionQuery cq; + if (CCollisionHandler::DetectHit(unit, unit->GetTransformMatrix(true), pos, pos + dir * maxLen, &cq, true)) { + const float len = cq.GetHitPosDist(pos, dir); + if (len > maxLen) // possibly a bug in CCollisionHandler::DetectHit? + continue; + hits.emplace_back(len, unit->id, "unit"); + } + } + } + + if (testFeatures) { + for (const auto *feature : quad.features) { + if (!feature->HasCollidableStateBit(CSolidObject::CSTATE_BIT_QUADMAPRAYS)) + continue; + + if (!testedFeatureIDs.insert(feature->id).second) + continue; + + if (!LuaUtils::IsFeatureVisible(L, feature)) + continue; + + CollisionQuery cq; + if (CCollisionHandler::DetectHit(feature, feature->GetTransformMatrix(true), pos, pos + dir * maxLen, &cq, true)) { + const float len = cq.GetHitPosDist(pos, dir); + if (len > maxLen) + continue; + hits.emplace_back(len, feature->id, "feature"); + } + } + } + } + + std::stable_sort(hits.begin(), hits.end(), [] (const auto& a, const auto& b) { + return std::get<0>(a) < std::get<0>(b); + }); + + lua_createtable(L, hits.size(), 0); + + int num = 0; + for (const auto& [hitLength, objectID, objectType] : hits) { + lua_createtable(L, 3, 0); + + lua_pushnumber(L, hitLength); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, objectID); + lua_rawseti(L, -2, 2); + lua_pushstring(L, objectType); + lua_rawseti(L, -2, 3); + + lua_rawseti(L, -2, ++num); + } + + return 1; +} + +/*** Traces a ray from a position in a direction + * + * @function Spring.TraceRayInDirection + * + * Returns all unit and/or feature hits along a ray, sorted by distance + * from the start position. + * + * @param posX number + * @param posY number + * @param posZ number + * @param dirX number + * @param dirY number + * @param dirZ number + * @param maxLength number + * @param type string Object type to test: `"unit"`, `"feature"`, or `"both"` + * @return table[] hits Array of `{hitLength, objectID, objectType}` entries + */ +int LuaSyncedRead::TraceRayInDirection(lua_State* L) +{ + float3 pos(luaL_checkfloat(L, 1), luaL_checkfloat(L, 2), luaL_checkfloat(L, 3)); + float3 dir(luaL_checkfloat(L, 4), luaL_checkfloat(L, 5), luaL_checkfloat(L, 6)); + const float maxLen = luaL_optfloat(L, 7, 999999.f); + const char* type = luaL_checkstring(L, 8); + return TraceRayImpl(L, pos, dir, maxLen, type); +} + +/*** Traces a ray between two positions + * + * @function Spring.TraceRayBetweenPositions + * + * Checks for unit and/or feature collisions between two positions + * and returns all hits sorted by distance from the start position. + * + * @param startX number + * @param startY number + * @param startZ number + * @param endX number + * @param endY number + * @param endZ number + * @param type string Object type to test: `"unit"`, `"feature"`, or `"both"` + * @return table[] hits Array of `{hitLength, objectID, objectType}` entries + */ +int LuaSyncedRead::TraceRayBetweenPositions(lua_State* L) +{ + float3 start(luaL_checkfloat(L, 1), luaL_checkfloat(L, 2), luaL_checkfloat(L, 3)); + float3 end(luaL_checkfloat(L, 4), luaL_checkfloat(L, 5), luaL_checkfloat(L, 6)); + const char* type = luaL_checkstring(L, 7); + const auto [dir, length] = (end - start).GetNormalized(); + return TraceRayImpl(L, start, dir, length, type); +} + static int TraceRayGroundImpl(lua_State *const L, const float3 &pos, const float3 &dir, const float maxLen, const bool testWater) { const float rayLength = CGround::LineGroundWaterCol(pos, dir, maxLen, testWater, CLuaHandle::GetHandleSynced(L)); diff --git a/rts/Lua/LuaSyncedRead.h b/rts/Lua/LuaSyncedRead.h index 98c632ad9b0..dbec13c0ab7 100644 --- a/rts/Lua/LuaSyncedRead.h +++ b/rts/Lua/LuaSyncedRead.h @@ -302,9 +302,8 @@ class LuaSyncedRead { static int GetRadarErrorParams(lua_State* L); - static int TraceRay(lua_State* L); //TODO: not implemented - static int TraceRayUnits(lua_State* L); //TODO: not implemented - static int TraceRayFeatures(lua_State* L); //TODO: not implemented + static int TraceRayInDirection(lua_State* L); + static int TraceRayBetweenPositions(lua_State* L); static int TraceRayGroundBetweenPositions(lua_State* L); static int TraceRayGroundInDirection(lua_State* L); }; From f1df2dc1874394e9609ccc5a037c2222aa1f702c Mon Sep 17 00:00:00 2001 From: sprunk Date: Thu, 2 Jul 2026 22:25:29 +0200 Subject: [PATCH 26/39] Fix #3019, missing include --- test/other/testMutex.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/other/testMutex.cpp b/test/other/testMutex.cpp index 0a84f129e45..b8e3ca5ec68 100644 --- a/test/other/testMutex.cpp +++ b/test/other/testMutex.cpp @@ -14,6 +14,7 @@ #ifndef _WIN32 #include #include + #include #endif #ifdef _WIN32 From a15b7b3a1da6ec8f5025ed25df9efcbe81156237 Mon Sep 17 00:00:00 2001 From: sprunk Date: Sat, 30 May 2026 22:13:32 +0200 Subject: [PATCH 27/39] Fix #2987, edge scrolling threshold --- rts/Game/Camera.cpp | 4 ++-- rts/Game/Camera.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rts/Game/Camera.cpp b/rts/Game/Camera.cpp index 8c5ea462a4b..921c1b9ffb5 100644 --- a/rts/Game/Camera.cpp +++ b/rts/Game/Camera.cpp @@ -746,8 +746,8 @@ float3 CCamera::GetMoveVectorFromState(bool fromKeyState) const } int2 border; - border.x = std::max(1, windowW * edgeMoveWidth); - border.y = std::max(1, viewH * edgeMoveWidth); + border.x = std::max(1, static_cast (windowW * edgeMoveWidth)); + border.y = std::max(1, static_cast ( viewH * edgeMoveWidth)); float2 move; // must be float, ints don't save the sign in case of 0 and we need it for copysign() diff --git a/rts/Game/Camera.h b/rts/Game/Camera.h index 7901f2218e2..5a114e152f8 100644 --- a/rts/Game/Camera.h +++ b/rts/Game/Camera.h @@ -284,7 +284,7 @@ class CCamera { */ float moveSlowMult; - int edgeMoveWidth; + float edgeMoveWidth; int useInterpolate; bool edgeMoveDynamic; From 2b8528322d8e8ba169d347f48543c48dd8b1bbf2 Mon Sep 17 00:00:00 2001 From: sprunk Date: Sat, 11 Apr 2026 23:14:28 +0200 Subject: [PATCH 28/39] Add type checking to ADD_FOO Lua defs macros --- rts/Lua/LuaDefs.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/rts/Lua/LuaDefs.h b/rts/Lua/LuaDefs.h index 091f2bb5f6e..2959e058afd 100644 --- a/rts/Lua/LuaDefs.h +++ b/rts/Lua/LuaDefs.h @@ -60,16 +60,18 @@ namespace { // Requires a "start" address, use ADDRESS() #define ADD_INT(lua, cpp) \ + static_assert(std::is_integral_v, \ + "Lua API break! Add DataElement manually"); \ paramMap[lua] = DataElement(GetDataType(cpp), ADDRESS(cpp) - start) -#define ADD_BOOL(lua, cpp) \ +#define ADD_SIMPLE_DATA(lua, cpp, expected_type) \ + static_assert(std::is_same_v, \ + "Lua API break! Add DataElement manually"); \ paramMap[lua] = DataElement(GetDataType(cpp), ADDRESS(cpp) - start) -#define ADD_FLOAT(lua, cpp) \ - paramMap[lua] = DataElement(GetDataType(cpp), ADDRESS(cpp) - start) - -#define ADD_STRING(lua, cpp) \ - paramMap[lua] = DataElement(GetDataType(cpp), ADDRESS(cpp) - start) +#define ADD_BOOL(lua, cpp) ADD_SIMPLE_DATA(lua, cpp, bool) +#define ADD_FLOAT(lua, cpp) ADD_SIMPLE_DATA(lua, cpp, float) +#define ADD_STRING(lua, cpp) ADD_SIMPLE_DATA(lua, cpp, std::string) #define ADD_FUNCTION(lua, cpp, func) \ paramMap[lua] = DataElement(FUNCTION_TYPE, ADDRESS(cpp) - start, func) From ed68dc1c023ce334669bcfbfb0990eaac3ad6d32 Mon Sep 17 00:00:00 2001 From: sprunk Date: Fri, 1 May 2026 21:37:43 +0200 Subject: [PATCH 29/39] Remove legacy engineoptions * these no longer did anything and just polluted the interface. * also removed example gadgetry using them from basecontent. --- cont/LuaUI/debug.lua | 2 - cont/base/springcontent/CMakeLists.txt | 2 - cont/base/springcontent/EngineOptions.lua | 9 - .../LuaGadgets/Gadgets/README.txt | 1 - .../LuaGadgets/Gadgets/share_delayed.lua | 402 ------------------ .../LuaGadgets/Gadgets/unit_limit_dgun.lua | 155 ------- doc/StartScriptFormat.txt | 2 - .../content/articles/start-script-format.md | 2 - test/validation/prepare.sh | 1 - tools/benchmark/script_benchmark_zwzsg.txt | 1 - tools/scripts/runner.sh | 3 - 11 files changed, 580 deletions(-) delete mode 100644 cont/base/springcontent/LuaGadgets/Gadgets/share_delayed.lua delete mode 100644 cont/base/springcontent/LuaGadgets/Gadgets/unit_limit_dgun.lua diff --git a/cont/LuaUI/debug.lua b/cont/LuaUI/debug.lua index 26cb771850d..f938a8d9889 100644 --- a/cont/LuaUI/debug.lua +++ b/cont/LuaUI/debug.lua @@ -313,8 +313,6 @@ function Debug() print("Game.mapSizeZ = " .. Game.mapSizeZ) print("Game.mapName = " .. Game.mapName) print("Game.modName = " .. Game.modName) - print("Game.limitDGun = " .. tostring(Game.limitDGun)) - print("Game.Game.diminishingMetal = " .. tostring(Game.diminishingMetal)) PrintAllyTeamList() PrintTeamList() diff --git a/cont/base/springcontent/CMakeLists.txt b/cont/base/springcontent/CMakeLists.txt index 95e5769f31e..01509b86128 100644 --- a/cont/base/springcontent/CMakeLists.txt +++ b/cont/base/springcontent/CMakeLists.txt @@ -101,7 +101,6 @@ list(APPEND FILES LuaGadgets/Gadgets/share_levels.lua LuaGadgets/Gadgets/cmd_nocost.lua LuaGadgets/Gadgets/game_end.lua - LuaGadgets/Gadgets/share_delayed.lua LuaGadgets/Gadgets/README.txt LuaGadgets/Gadgets/share_no_builders.lua LuaGadgets/Gadgets/object_statusbars_default.lua @@ -109,7 +108,6 @@ list(APPEND FILES LuaGadgets/Gadgets/share_control.lua LuaGadgets/Gadgets/unit_script.lua LuaGadgets/Gadgets/game_spawn.lua - LuaGadgets/Gadgets/unit_limit_dgun.lua LuaGadgets/system.lua LuaGadgets/actions.lua LuaGadgets/README.txt diff --git a/cont/base/springcontent/EngineOptions.lua b/cont/base/springcontent/EngineOptions.lua index f1b0bfc0de8..23dd42ce902 100644 --- a/cont/base/springcontent/EngineOptions.lua +++ b/cont/base/springcontent/EngineOptions.lua @@ -38,15 +38,6 @@ local options = step = 1, -- quantization is aligned to the def value -- (step <= 0) means that there is no quantization }, - - { - key = 'LimitDgun', - name = 'Limit D-Gun range', - desc = "The commander's D-Gun weapon will be usable only close to the player's starting location", - type = 'bool', - def = false, - }, - { key = 'GhostedBuildings', name = 'Ghosted buildings', diff --git a/cont/base/springcontent/LuaGadgets/Gadgets/README.txt b/cont/base/springcontent/LuaGadgets/Gadgets/README.txt index c859ac704bb..1fa49aa2a23 100644 --- a/cont/base/springcontent/LuaGadgets/Gadgets/README.txt +++ b/cont/base/springcontent/LuaGadgets/Gadgets/README.txt @@ -9,7 +9,6 @@ LuaRules * cmd_nocost.lua * share_control.lua -* share_delayed.lua * share_levels.lua * share_no_builders.lua diff --git a/cont/base/springcontent/LuaGadgets/Gadgets/share_delayed.lua b/cont/base/springcontent/LuaGadgets/Gadgets/share_delayed.lua deleted file mode 100644 index f19b841d48f..00000000000 --- a/cont/base/springcontent/LuaGadgets/Gadgets/share_delayed.lua +++ /dev/null @@ -1,402 +0,0 @@ --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- --- --- file: share_delayed.lua --- brief: delay unit sharing --- author: Dave Rodgers --- --- Copyright (C) 2007. --- Licensed under the terms of the GNU GPL, v2 or later. --- --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -function gadget:GetInfo() - return { - name = "SharingDelayed", - desc = "delayed unit sharing", - author = "trepan", - date = "Apr 22, 2007", - license = "GNU GPL, v2 or later", - layer = -4, - enabled = true -- loaded by default? - } -end - --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- --- --- FIXME: (TODO) --- - Delayed resource sharing --- - Visual indicators for units queued to be shared --- - Update unit share times for fallen comrades --- --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - --- Only active in comm-ends games - -local gameMode = Game.gameMode or 4 - -if (gameMode ~= 1) then - return false -end - - --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- --- --- Proposed Command ID Ranges: --- --- all negative: Engine (build commands) --- 0 - 999: Engine --- 1000 - 9999: Group AI --- 10000 - 19999: LuaUI --- 20000 - 29999: LuaCob --- 30000 - 39999: LuaRules --- - -local CMD_CANCEL_SHARE = 33999 - - --------------------------------------------------------------------------------- --- COMMON --------------------------------------------------------------------------------- -if (gadgetHandler:IsSyncedCode()) then --------------------------------------------------------------------------------- --- SYNCED --------------------------------------------------------------------------------- - -local teams = {} -- teamID = { unitID = shareInfo } -local shares = {} -- unitID = oldTeam -local frames = {} -- unitID = framee - -local enabled = true - -local minDelay = 1 -- minimum delay between shares -local costScale = 0.1 -- add extra cycles based on unit costs - -local cancelShareCmdDesc = { - id = CMD_CANCEL_SHARE, - type = CMDTYPE.ICON, - name = '\255\255\100\100NoShare', - tooltip = 'Cancel the unit transfer', - action = 'cancel_share', -} - - --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -local function AllowAction(playerID) - if (playerID ~= 0) then - Spring.SendMessageToPlayer(playerID, "Must be the host player") - return false - end - if (not Spring.IsCheatingEnabled()) then - Spring.SendMessageToPlayer(playerID, "Cheating must be enabled") - return false - end - return true -end - - -local function ChatControl(cmd, line, words, playerID) - if (not AllowAction(playerID)) then - Spring.Echo('delayed sharing is ' .. (enabled and 'enabled' or 'disabled')) - return true - end - if (#words == 0) then - enabled = not enabled - else - enabled = (words[1] == '1') - end - Spring.Echo('delayed sharing is ' .. (enabled and 'enabled' or 'disabled')) - return true -end - - -local function StopShare(cmd, line, words, playerID) - local _,_,_,teamID = Spring.GetPlayerInfo(playerID) - local team = teamID and teams[teamID] or nil - if (team) then - for _,data in pairs(team) do - shares[data.unitID] = nil - frames[data.unitID] = nil - end - teams[teamID] = nil - Spring.Echo('cancelled remaining unit transfers') - else - Spring.Echo('there are no unit transfers to cancel') - end - return true -end - - --------------------------------------------------------------------------------- - -function gadget:Initialize() - gadgetHandler:RegisterCMDID(CMD_CANCEL_SHARE) - _G.shareFrames = frames - - local cmd, help - - cmd = "sharedelay" - help = " [0|1]: delayed unit sharing, useful for comm ends games" - gadgetHandler:AddChatAction(cmd, ChatControl, help) - Script.AddActionFallback(cmd .. ' ', help) - - cmd = "stopshare" - help = ": cancel all queued unit transfers for your team" - gadgetHandler:AddChatAction(cmd, StopShare, help) - Script.AddActionFallback(cmd, help) -end - - -function gadget:Shutdown() - gadgetHandler:RemoveChatAction("sharedelay") - Script.RemoveActionFallback("sharedelay") - - gadgetHandler:RemoveChatAction("stopshare") - Script.RemoveActionFallback("stopshare") - - for _,unitID in ipairs(Spring.GetAllUnits()) do - local cmdDescID = Spring.FindUnitCmdDesc(unitID, CMD_CANCEL_SHARE) - if (cmdDescID) then - Spring.RemoveUnitCmdDesc(unitID, cmdDescID) - end - end -end - - --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -local insert = table.insert -local remove = table.remove - - -local function InsertShare(unitID, oldTeam, newTeam, delay) - if (shares[unitID]) then - return -- already active - end - - local shareInfo = { - unitID = unitID, - oldTeam = oldTeam, - newTeam = newTeam, - delay = delay, - } - local team = teams[oldTeam] - - if (team) then - print(team[#team].frame, delay) - shareInfo.frame = team[#team].frame + delay - else - team = {} - teams[oldTeam] = team - shareInfo.frame = Spring.GetGameFrame() + delay - end - - insert(team, shareInfo) - shares[unitID] = oldTeam - frames[unitID] = shareInfo.frame - - Spring.InsertUnitCmdDesc(unitID, 1, cancelShareCmdDesc) -end - - --------------------------------------------------------------------------------- - -local function RecalcDelays(team) - for i = 2, #team do - team[i].frame = team[i - 1].frame + team[i].delay - frames[team[i].unitID] = team[i].frame - end -end - - -local function RemoveShare(unitID) - local oldTeam = shares[unitID] - shares[unitID] = nil - frames[unitID] = nil - - local cmdDescID = Spring.FindUnitCmdDesc(unitID, CMD_CANCEL_SHARE) - if (cmdDescID) then - Spring.RemoveUnitCmdDesc(unitID, cmdDescID) - end - - local team = teams[oldTeam] - if ((oldTeam == nil) or (team == nil)) then - return -- not active - end - - local index - for i, shareInfo in ipairs(team) do - if (shareInfo.unitID == unitID) then - index = i - break - end - end - if (index == nil) then - return -- something is amiss - end - - local shareInfo = team[index] - remove(team, index) - - if (#team <= 0) then - teams[oldTeam] = nil - else - if (index ~= 1) then - RecalcDelays(team) - else - local nowFrame = Spring.GetGameFrame() - if (shareInfo.frame > nowFrame) then - local front = team[1] - front.frame = nowFrame + front.delay - frames[front.unitID] = front.frame - RecalcDelays(team) - end - end - end -end - - --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -function gadget:AllowUnitTransfer(unitID, unitDefID, oldTeam, newTeam, capture) - if (capture) then - return true - end - if (not enabled) then - return true - end - - local ud = UnitDefs[unitDefID] - if (not ud) then - return true -- something is borked - end - - -- compute the share delay - local cost = ud.metalCost + (ud.energyCost / 60) - local costDelay = math.floor(cost * costScale) - local shareDelay = minDelay + costDelay - - local team = teams[oldTeam] - if ((team == nil) and (shareDelay <= 0)) then - return true -- share the unit immediately - end - - InsertShare(unitID, oldTeam, newTeam, shareDelay) - - return false -end - - --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -function gadget:GameFrame(frameNum) - for _,team in pairs(teams) do - while (team and (#team > 0)) do - local front = team[1] - if (front.frame > frameNum) then - break -- front is not yet ready to be shared - end - - local curTeam = Spring.GetUnitTeam(front.unitID) - if (curTeam and (curTeam == front.oldTeam)) then - -- FIXME: see if newTeam is alive - local tmp = AllowUnitTransfer - AllowUnitTransfer = function() return true end - Spring.TransferUnit(front.unitID, front.newTeam) - AllowUnitTransfer = tmp - end - - RemoveShare(front.unitID) - end - end -end - - --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -function gadget:AllowCommand(unitID, unitDefID, unitTeam, - cmdID, cmdParams, cmdOptions) - if (cmdID == CMD_CANCEL_SHARE) then - RemoveShare(unitID) - return false - end - return true -end - - --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -function gadget:UnitDestroyed(unitID, unitDefID, unitTeam) - RemoveShare(unitID) -end - - --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -function gadget:UnitTaken(unitID) - RemoveShare(unitID) -end - - --------------------------------------------------------------------------------- --- SYNCED --------------------------------------------------------------------------------- -else --------------------------------------------------------------------------------- --- UNSYNCED --------------------------------------------------------------------------------- - -function gadget:Initialize() -end - - -function gadget:Shutdown() -end - - -local GetGameFrame = Spring.GetGameFrame -local GetUnitPosition = Spring.GetUnitPosition -local GetUnitAllyTeam = Spring.GetUnitAllyTeam -local GetLocalAllyTeamID = Spring.GetLocalAllyTeamID -local AddWorldIcon = Spring.AddWorldIcon -local AddWorldText = Spring.AddWorldText - -function gadget:DrawWorld() - local frames = SYNCED.shareFrames - if ((frames == nil) or (snext(frames) == nil)) then - return - end - local nowFrame = GetGameFrame() - local myAllyTeam = GetLocalAllyTeamID() - for unitID, frame in spairs(frames) do - if (GetUnitAllyTeam(unitID) == myAllyTeam) then - local x, y, z = GetUnitPosition(unitID) - if (x) then - local str = string.format('%.1f', (frame - nowFrame) / Game.gameSpeed) - AddWorldIcon(x, y, z, CMD.STOP) - AddWorldText(str, x, y, z) - end - end - end -end - - --------------------------------------------------------------------------------- --- UNSYNCED --------------------------------------------------------------------------------- -end --------------------------------------------------------------------------------- --- COMMON --------------------------------------------------------------------------------- diff --git a/cont/base/springcontent/LuaGadgets/Gadgets/unit_limit_dgun.lua b/cont/base/springcontent/LuaGadgets/Gadgets/unit_limit_dgun.lua deleted file mode 100644 index e5bcf08e67e..00000000000 --- a/cont/base/springcontent/LuaGadgets/Gadgets/unit_limit_dgun.lua +++ /dev/null @@ -1,155 +0,0 @@ --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- --- --- file: unit_dgun_limit.lua --- brief: Re-implements limit dgun in Lua --- author: Andrea Piras --- --- Copyright (C) 2010. --- Licensed under the terms of the GNU GPL, v2 or later. --- --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -function gadget:GetInfo() - return { - name = "Limit Dgun", - desc = "Re-implements limit dgun in Lua", - author = "Andrea Piras", - date = "August, 2010", - license = "GNU GPL, v2 or later", - layer = 0, - enabled = true -- loaded by default? - } -end - --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -local enabled = tonumber(Spring.GetModOptions().limitdgun) or 0 -if (enabled == 0) then - return false -end - -local dgunMapFraction = 3 -- use the modoption to define it instead? -local dgunRadiusSquared = (Game.mapSizeX*Game.mapSizeZ) / (dgunMapFraction*dgunMapFraction) -local dgunRadius = math.sqrt(dgunRadiusSquared) - -if (not gadgetHandler:IsSyncedCode()) then --begin unsynced section - -local glColor = gl.Color -local glDrawGroundCircle = gl.DrawGroundCircle -local glPushMatrix = gl.PushMatrix -local glPopMatrix = gl.PopMatrix -local glDeleteList = gl.DeleteList -local glCreateList = gl.CreateList -local glCallList = gl.CallList - -local GetSpectatingState = Spring.GetSpectatingState -local GetTeamStartPosition = Spring.GetTeamStartPosition -local GetTeamList = Spring.GetTeamList -local GetTeamStartPosition = Spring.GetTeamStartPosition -local AreTeamsAllied = Spring.AreTeamsAllied -local GetGaiaTeamID = Spring.GetGaiaTeamID -local GetMyTeamID = Spring.GetMyTeamID -local GetTeamInfo = Spring.GetTeamInfo - -local allyTeamVec = Spring.GetAllyTeamList() -local myTeamID = GetMyTeamID() -local gaiaTeamID = GetGaiaTeamID() -local circlesList - -function ReGenerateDisplayList() - if circlesList then - glDeleteList(circlesList) - end - circlesList = glCreateList( function() - for allyIndex,allyTeamID in ipairs(allyTeamVec) do - local teamList = GetTeamList(allyTeamID) - for _,teamID in ipairs(teamList) do - local _,_,isDead = GetTeamInfo(teamID) - if not isDead and teamID ~= gaiaTeamID then - if teamID == myTeamID then - glColor(0.0, 0.0, 1.0, 0.6) -- use blue circles for your range - elseif AreTeamsAllied( teamID, myTeamID ) == true then -- order matters! this tells if he's allied with us, not vice-versa - glColor(1.0, 1.0, 1.0, 0.6) -- use white circles for ally ranges - else - glColor(1.0, 1.0, 0.0, 0.6) -- use yellow circles for enemy ranges - end - local teamX,teamY,teamZ = GetTeamStartPosition(teamID) - if teamX and teamY and teamZ then - glDrawGroundCircle(teamX,teamY,teamZ, dgunRadius, 40) - end - end - end - end - end) -end - -function gadget:PlayerChanged() -- regenerate the display list, update even if it's not for ourself, so we can rid of unnecessary team circles - ReGenerateDisplayList() -end - -function gadget:Initialize() - ReGenerateDisplayList() -end - -function gadget:Shutdown() - glDeleteList(circlesList) -end - -function gadget:DrawWorld() -- paint dgun limit preview range circle - if circlesList then - glPushMatrix() - glCallList(circlesList) - glPopMatrix() - end -end - -else -- begin synced section - -local GetTeamStartPosition = Spring.GetTeamStartPosition -local GetUnitPosition = Spring.GetUnitPosition -local CMD_MANUALFIRE = CMD.MANUALFIRE - -local teamStartPosVec = {} -- cache for team starting pos - --- squared distance between 2 points in 3D cartesian space -function SqDistance(one, two) - dx = one[1] - two[1] - dy = one[2] - two[2] - dz = one[3] - two[3] - return dx*dx + dy*dy + dz*dz -end - -function gadget:AllowCommand(unitID, unitDefID, teamID, cmdID, cmdParams, cmdOptions, cmdTag, synced) - if cmdID ~= CMD_MANUALFIRE then -- pass all non-dgun commands - return true - end - - if not teamStartPosVec[teamID] then -- build cache if not available - local teamX,teamY,teamZ = GetTeamStartPosition(teamID) - if teamX and teamY and teamZ then - teamStartPosVec[teamID] = {teamX,teamY,teamZ} - end - end - local teamStartPos = teamStartPosVec[teamID] - if not teamStartPos then -- wtf - return true - end - - local unitX, unitY, unitZ = GetUnitPosition(unitID) - if not unitX or not unitY or not unitZ then -- wtf - return true - end - local unitPos = {unitX,unitY,unitZ} - - -- restrict the command to the allowed area - if SqDistance(teamStartPos,unitPos) > dgunRadiusSquared then -- unit outside of allowed range, block the command - return false - end - - return true -end - -end -- end synced section diff --git a/doc/StartScriptFormat.txt b/doc/StartScriptFormat.txt index f7ce0a0c8e1..9c13689a6e4 100644 --- a/doc/StartScriptFormat.txt +++ b/doc/StartScriptFormat.txt @@ -137,8 +137,6 @@ StartMetal=1000; StartEnergy=1000; MaxUnits=500; // per team - GameMode=x; // 0 cmdr dead->game continues, 1 cmdr dead->game ends, 2 lineage, 3 openend - LimitDGun=0; // limit dgun to fixed radius around startpos? DisableMapDamage=0; // disable map craters? GhostedBuildings=1; // ghost enemy buildings after losing los on them NoHelperAIs=0; // are GroupAIs and other helper AIs allowed? diff --git a/doc/site/content/articles/start-script-format.md b/doc/site/content/articles/start-script-format.md index 75222119e85..df2300799df 100644 --- a/doc/site/content/articles/start-script-format.md +++ b/doc/site/content/articles/start-script-format.md @@ -143,8 +143,6 @@ draft = false StartMetal=1000; StartEnergy=1000; MaxUnits=500; // per team - GameMode=x; // 0 cmdr dead->game continues, 1 cmdr dead->game ends, 2 lineage, 3 openend - LimitDGun=0; // limit dgun to fixed radius around startpos? DisableMapDamage=0; // disable map craters? GhostedBuildings=1; // ghost enemy buildings after losing los on them NoHelperAIs=0; // are GroupAIs and other helper AIs allowed? diff --git a/test/validation/prepare.sh b/test/validation/prepare.sh index e62c45dd675..1fa1e4adf45 100755 --- a/test/validation/prepare.sh +++ b/test/validation/prepare.sh @@ -33,7 +33,6 @@ cat <game continues, 1 cmdr dead->game ends - LimitDgun=1; // limit dgun to fixed radius around startpos? - DiminishingMMs=0; // diminish metal maker's metal production for every new one of them? DisableMapDamage=0; // disable map craters? GhostedBuildings=1; // ghost enemy buildings after losing los on them From 615d257c1293af91df4c0aff1b563c1eb9f0d248 Mon Sep 17 00:00:00 2001 From: lostsquirrel1 Date: Mon, 6 Jul 2026 12:36:20 +0100 Subject: [PATCH 30/39] fix desync --- rts/Sim/Path/QTPFS/PathManager.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/rts/Sim/Path/QTPFS/PathManager.cpp b/rts/Sim/Path/QTPFS/PathManager.cpp index 1aa5e9a36c5..247a277049c 100644 --- a/rts/Sim/Path/QTPFS/PathManager.cpp +++ b/rts/Sim/Path/QTPFS/PathManager.cpp @@ -933,6 +933,15 @@ bool QTPFS::PathManager::InitializeSearch(QTPFS::entity searchEntity) { assert((registry.any_of(pathEntity))); IPath* path = GetPath(pathEntity); assert(path->GetPathType() == pathType); + + // Somehow units can get wiped without triggering a delete. This is a catch for that until the + // cause can be found and resolved. + const CSolidObject* owner = path->GetOwner(); + if (owner != nullptr) { + if (owner->objectUsable == false) + return false; + } + search->Initialize(&nodeLayer, path->GetSourcePoint(), path->GetGoalPosition(), path->GetOwner()); path->SetHash(search->GetHash()); path->SetVirtualHash(search->GetPartialSearchHash()); @@ -1122,20 +1131,13 @@ bool QTPFS::PathManager::ExecuteSearch( int currentThread = ThreadPool::GetThreadNum(); assert(search != nullptr); + assert(search->initialized); // temp-path might have been removed already via // DeletePath before we got a chance to process it if (path == nullptr) return false; - // Somehow units can get wiped without triggering a delete. This is a catch for that until the - // cause can be found and resolved. - const CSolidObject* owner = path->GetOwner(); - if (owner != nullptr) { - if (owner->objectUsable == false) - return false; - } - assert(path->GetID() == search->GetID()); bool forceFullPath = false; From c420138585db7d744be856cbb381d32dc4683576 Mon Sep 17 00:00:00 2001 From: PtaQQ Date: Tue, 23 Jun 2026 20:33:13 +0200 Subject: [PATCH 31/39] SpringMath: add Catmull-Rom bicubic interpolation helpers CatmullRom does C1-continuous cubic interpolation through two samples given their outer neighbours; InterpolateBicubic applies it over a 4x4 patch. Used by the smooth mesh sampler for a crease-free surface. Co-Authored-By: Claude Opus 4.8 (1M context) --- rts/System/SpringMath.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rts/System/SpringMath.h b/rts/System/SpringMath.h index 2d3af6ffe9b..f49871c778c 100644 --- a/rts/System/SpringMath.h +++ b/rts/System/SpringMath.h @@ -175,6 +175,26 @@ template constexpr T mixRotation(T v1, T v2, T2 a) { template constexpr T Blend(const T v1, const T v2, const float a) { return mix(v1, v2, a); } +// Catmull-Rom cubic interpolation through p1..p2, with p0/p3 the outer neighbours, t in [0,1]. +// C1-continuous (matching gradients across segment borders), unlike linear mix. +constexpr float CatmullRom(float p0, float p1, float p2, float p3, float t) +{ + return p1 + 0.5f * t * ((p2 - p0) + t * ((2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) + t * (3.0f * (p1 - p2) + p3 - p0))); +} + +// Bicubic Catmull-Rom over a 4x4 patch of samples p[row][col]; dx/dy are the +// fractional offsets in [0,1] from the inner sample p[1][1] toward p[2][2]. +inline float InterpolateBicubic(const float p[4][4], float dx, float dy) +{ + const float cols[4] = { + CatmullRom(p[0][0], p[0][1], p[0][2], p[0][3], dx), + CatmullRom(p[1][0], p[1][1], p[1][2], p[1][3], dx), + CatmullRom(p[2][0], p[2][1], p[2][2], p[2][3], dx), + CatmullRom(p[3][0], p[3][1], p[3][2], p[3][3], dx), + }; + return CatmullRom(cols[0], cols[1], cols[2], cols[3], dy); +} + int Round(const float f) _const _warn_unused_result; template constexpr T Square(const T x) { return x*x; } From 6eecb24c143941337574962ccfea453a776f100a Mon Sep 17 00:00:00 2001 From: rlcevg <514675+rlcevg@users.noreply.github.com> Date: Sat, 4 Jul 2026 22:19:50 +0300 Subject: [PATCH 32/39] Update BARb 1.6.26 --- AI/Skirmish/BARb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Skirmish/BARb b/AI/Skirmish/BARb index 0ef36267633..9875f1b332a 160000 --- a/AI/Skirmish/BARb +++ b/AI/Skirmish/BARb @@ -1 +1 @@ -Subproject commit 0ef36267633d6c1b2f6408a8d8a59fff38745dc3 +Subproject commit 9875f1b332aa037c7a9f6e8bea1f8ecd9f289096 From 62206aa0d36422b1170cca84f032750126651e71 Mon Sep 17 00:00:00 2001 From: Luis Valencia <244623113+L01sValencia@users.noreply.github.com> Date: Wed, 8 Jul 2026 17:44:16 -0600 Subject: [PATCH 33/39] fix: links to other getting-started pages --- .../getting-started/first-steps-with-the-engine.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/site/content/docs/guides/getting-started/first-steps-with-the-engine.md b/doc/site/content/docs/guides/getting-started/first-steps-with-the-engine.md index 2f023a572ac..a30c72ad182 100644 --- a/doc/site/content/docs/guides/getting-started/first-steps-with-the-engine.md +++ b/doc/site/content/docs/guides/getting-started/first-steps-with-the-engine.md @@ -302,17 +302,17 @@ Two games are particularly suited for newcomers: 4. Edit Lua files and retest Changes to Lua code take effect on the next game start. For workflow tips, -see the [Lua Language Server guide](lua-language-server/) for setting up IDE +see the [Lua Language Server guide](lua-language-server.md) for setting up IDE autocompletion. ## Next Steps Once you have the engine running with a game and map: -- Learn about [VFS basics](vfs-basics/) to understand how the engine loads content -- Read about [Widgets and Gadgets](widgets-and-gadgets/) to understand Lua scripting -- Explore [basecontent](basecontent/) for the default scripts provided by the engine -- Check out [Unit Types Basics](unit-types-basics/) to define your first units +- Learn about [VFS basics](vfs-basics.md) to understand how the engine loads content +- Read about [Widgets and Gadgets](widgets-and-gadgets.md) to understand Lua scripting +- Explore [basecontent](basecontent.md) for the default scripts provided by the engine +- Check out [Unit Types Basics](unit-types-basics.md) to define your first units For API reference while developing, see the [Lua API documentation](/docs/lua-api/). @@ -340,4 +340,4 @@ error, see the [Write Directory](#write-directory) section above. Quick fixes: When in doubt, the engine sources contain the authoritative behavior. Search the [`rts/System/FileSystem/`](https://github.com/beyond-all-reason/RecoilEngine/tree/master/rts/System/FileSystem) -directory for data directory handling code. \ No newline at end of file +directory for data directory handling code. From 2d4b031d19583bfcdfe3d01964f0848e0e91f1d8 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Fri, 10 Jul 2026 00:55:39 +0100 Subject: [PATCH 34/39] Fix LuaSocket VFS mode error in LuaMenu --- rts/Lua/LuaHandle.cpp | 26 ++++++++++++++++++++++++++ rts/Lua/LuaHandle.h | 1 + rts/Lua/LuaMenu.cpp | 17 ----------------- rts/Lua/LuaMenu.h | 1 - rts/Lua/LuaUI.cpp | 20 -------------------- rts/Lua/LuaUI.h | 1 - 6 files changed, 27 insertions(+), 39 deletions(-) diff --git a/rts/Lua/LuaHandle.cpp b/rts/Lua/LuaHandle.cpp index 650a810499d..6ae29a6f293 100644 --- a/rts/Lua/LuaHandle.cpp +++ b/rts/Lua/LuaHandle.cpp @@ -41,6 +41,8 @@ #include "Sim/Units/UnitDef.h" #include "Sim/Weapons/Weapon.h" #include "Sim/Weapons/WeaponDef.h" +#include "System/FileSystem/FileHandler.h" +#include "System/FileSystem/VFSModes.h" #include "System/creg/SerializeLuaState.h" #include "System/Config/ConfigHandler.h" #include "System/EventHandler.h" @@ -56,6 +58,8 @@ #include "LuaInclude.h" +#include "lib/luasocket/src/luasocket.h" + #include #include #include @@ -549,6 +553,28 @@ bool CLuaHandle::LoadCode(lua_State* L, std::string code, const string& debug) } +void CLuaHandle::InitLuaSocket(lua_State* L) +{ + RECOIL_DETAILED_TRACY_ZONE; + + const std::string filename = "LuaSocket/socket.lua"; + CFileHandler f(filename, SPRING_VFS_BASE); + if (!f.FileExists()) { + LOG_L(L_ERROR, "Error loading %s (file does not exist)", filename.c_str()); + return; + } + + LUA_OPEN_LIB(L, luaopen_socket_core); + + std::string code; + if (f.LoadStringData(code)) { + LoadCode(L, std::move(code), filename); + } else { + LOG_L(L_ERROR, "Error loading %s", filename.c_str()); + } +} + + int CLuaHandle::LoadStringData(lua_State* L) { RECOIL_DETAILED_TRACY_ZONE; diff --git a/rts/Lua/LuaHandle.h b/rts/Lua/LuaHandle.h index 5256825a2b6..97ddf4323c9 100644 --- a/rts/Lua/LuaHandle.h +++ b/rts/Lua/LuaHandle.h @@ -329,6 +329,7 @@ class CLuaHandle : public CEventClient bool AddBasicCalls(lua_State* L); bool AddCommonModules(lua_State* L); bool LoadCode(lua_State* L, std::string code, const std::string& debug); + void InitLuaSocket(lua_State* L); static bool AddEntriesToTable(lua_State* L, const char* name, bool (*entriesFunc)(lua_State*)); /// returns error code and sets traceback on error diff --git a/rts/Lua/LuaMenu.cpp b/rts/Lua/LuaMenu.cpp index bafbc08be23..bce52197ec8 100644 --- a/rts/Lua/LuaMenu.cpp +++ b/rts/Lua/LuaMenu.cpp @@ -24,7 +24,6 @@ #include "System/FileSystem/FileHandler.h" #include "System/FileSystem/FileSystem.h" #include "System/Threading/SpringThreading.h" -#include "lib/luasocket/src/luasocket.h" #include "LuaUI.h" #include "System/Misc/TracyDefs.h" @@ -134,22 +133,6 @@ string CLuaMenu::LoadFile(const string& name) const } -void CLuaMenu::InitLuaSocket(lua_State* L) { - RECOIL_DETAILED_TRACY_ZONE; - std::string code; - std::string filename = "socket.lua"; - CFileHandler f(filename); - - LUA_OPEN_LIB(L, luaopen_socket_core); - - if (f.LoadStringData(code)) { - LoadCode(L, std::move(code), filename); - } else { - LOG_L(L_ERROR, "Error loading %s", filename.c_str()); - } -} - - bool CLuaMenu::RemoveSomeOpenGLFunctions(lua_State* L) { // remove some spring opengl functions that don't work preloading diff --git a/rts/Lua/LuaMenu.h b/rts/Lua/LuaMenu.h index 0c65859e90d..aa7a990475e 100644 --- a/rts/Lua/LuaMenu.h +++ b/rts/Lua/LuaMenu.h @@ -61,7 +61,6 @@ class CLuaMenu : public CLuaHandle static bool LoadUnsyncedReadFunctions(lua_State* L); static bool RemoveSomeOpenGLFunctions(lua_State* L); static bool PushGameVersion(lua_State* L); - void InitLuaSocket(lua_State* L); protected: QueuedAction queuedAction; }; diff --git a/rts/Lua/LuaUI.cpp b/rts/Lua/LuaUI.cpp index 01204219fa4..4b768f39f53 100644 --- a/rts/Lua/LuaUI.cpp +++ b/rts/Lua/LuaUI.cpp @@ -40,7 +40,6 @@ #include "System/Config/ConfigHandler.h" #include "System/StringUtil.h" #include "System/Threading/SpringThreading.h" -#include "lib/luasocket/src/luasocket.h" #include @@ -207,25 +206,6 @@ GetWatchDef(Explosion) SetWatchDef(Explosion) -void CLuaUI::InitLuaSocket(lua_State* L) { - std::string code; - std::string filename = "LuaSocket/socket.lua"; - CFileHandler f(filename, SPRING_VFS_BASE); - - if (!f.FileExists()) { - LOG_L(L_ERROR, "Error loading %s (file does not exist)", filename.c_str()); - return; - } - - LUA_OPEN_LIB(L, luaopen_socket_core); - - if (f.LoadStringData(code)) { - LoadCode(L, std::move(code), filename); - } else { - LOG_L(L_ERROR, "Error loading %s", filename.c_str()); - } -} - string CLuaUI::LoadFile(const string& name, const std::string& mode) const { CFileHandler f(name, mode); diff --git a/rts/Lua/LuaUI.h b/rts/Lua/LuaUI.h index 6ee9b2d1f73..dfe2b9ab2a4 100644 --- a/rts/Lua/LuaUI.h +++ b/rts/Lua/LuaUI.h @@ -83,7 +83,6 @@ class CLuaUI : public CLuaHandle string LoadFile(const string& name, const std::string& mode) const; bool LoadCFunctions(lua_State* L); - void InitLuaSocket(lua_State* L); bool BuildCmdDescTable(lua_State* L, const vector& cmds); bool GetLuaIntMap(lua_State* L, int index, spring::unordered_map& intList); From 21b121c2fe7f969eb6a10ccde4df8f2d8c472a7a Mon Sep 17 00:00:00 2001 From: Luis Valencia <244623113+L01sValencia@users.noreply.github.com> Date: Thu, 9 Jul 2026 18:25:54 -0600 Subject: [PATCH 35/39] Fix various typos in site articles (#3090) --- .../guides/getting-started/getting-started-with-rmlui.md | 6 +++--- .../docs/guides/getting-started/widgets-and-gadgets.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/site/content/docs/guides/getting-started/getting-started-with-rmlui.md b/doc/site/content/docs/guides/getting-started/getting-started-with-rmlui.md index b99aa69f3f7..2d21709bd9a 100644 --- a/doc/site/content/docs/guides/getting-started/getting-started-with-rmlui.md +++ b/doc/site/content/docs/guides/getting-started/getting-started-with-rmlui.md @@ -5,9 +5,9 @@ draft = false author = "Slashscreen" +++ -RmlUi is a UI framework that is defined using a HTML/CSS style workflow (using Lua instead of JS) intended to simplify UI development especially for those already familiar with web development. It is designed for interactive applications, and so is reactive by default. You can learn more about it on the [RmlUI website] and [differences in the Recoil version here](#differences-between-upstream-rmlui-and-rmlui-in-recoil). +RmlUi is a UI framework that is defined using a HTML/CSS style workflow (using Lua instead of JS) intended to simplify UI development especially for those already familiar with web development. It is designed for interactive applications, and so is reactive by default. You can learn more about it on the [RmlUi website] and [differences in the Recoil version here](#differences-between-upstream-rmlui-and-rmlui-in-recoil). -## How does RmlUI Work? +## How does RmlUi Work? To get started, it's important to learn a few key concepts. - Context: This is a bundle of documents and data models. @@ -416,7 +416,7 @@ document = widget.rmlContext:LoadDocument("document.rml", document_table) - The Beyond All Reason devs prefer to use one shared context for all rmlui widgets. -### Differences between upstream RmlUI and RmlUI in Recoil +### Differences between upstream RmlUi and RmlUi in Recoil - The SVG element allows either a filepath or raw SVG data in the src attribute, allowing for inline svg to be used (this may change to svg being supported between the opening and closing tag when implemented upstream) - An additional element `````` is available which allows for textures loaded in Recoil to be used, this behaves the same as an `````` element except the src attribute takes a [texture reference]({{% ref "articles/texture-reference-strings" %}}) diff --git a/doc/site/content/docs/guides/getting-started/widgets-and-gadgets.md b/doc/site/content/docs/guides/getting-started/widgets-and-gadgets.md index 216f9e21f72..6200e2f24b0 100644 --- a/doc/site/content/docs/guides/getting-started/widgets-and-gadgets.md +++ b/doc/site/content/docs/guides/getting-started/widgets-and-gadgets.md @@ -10,7 +10,7 @@ Before we get to Widgets and Gadgets we will start with an overview of some key - Synced mode is the environment that affects game simulation e.g ordering units around. Synced commands are distributed and run by all players in the game to ensure the simulation remains synced. - Unsynced mode is the players own environment, functionality here could for example enhance controls, display helpful information. -When in Unsynced mode LuaIntro, LuaUi, LuaRules and LuaGaia provide read access to synced but only LuaRules and LuaGaia have full access to simulation information (all players units etc.). In LuaIntro and LuaUi read access to synced is scoped to just what that player can see i.e. observing LoS & radar ranges. +When in Unsynced mode LuaIntro, LuaUI, LuaRules and LuaGaia provide read access to synced but only LuaRules and LuaGaia have full access to simulation information (all players units etc.). In LuaIntro and LuaUI read access to synced is scoped to just what that player can see i.e. observing LoS & radar ranges. ### Key Areas - LuaRules - Generally home to lower level customisations that affect unit behavior or overall game operation @@ -29,7 +29,7 @@ Widgets and Gadgets are concepts that have been adopted by several games using R Gadgets typically being lower level game logic, unit behaviour functionality that defines your game and should be present for all players, they are typically only found in LuaRules and LuaGaia and are often included using VFS.ZIP_ONLY so as not to be easily overridden by end users (your packaged version takes priority). -Widgets usually involve improving UX or showing helpful UI interfaces, and are often considered things that users can turn on/off in the game to suit their needs, be it through settings or a widget manager UI. They are usually specific to LuaIntro, LuaMenu and LuaUi. +Widgets usually involve improving UX or showing helpful UI interfaces, and are often considered things that users can turn on/off in the game to suit their needs, be it through settings or a widget manager UI. They are usually specific to LuaIntro, LuaMenu and LuaUI. To manage the invocation of Widgets & Gadgets a "**handler**" is setup in the Lua entrypoint. For environments with synced and unsyned entry points these typically use the same handler which calls the same widgets/gadgets but use a function the he handler like IsSyncedCode to check if they are being run in synced mode or unsynced mode and running the appropriate section of code. @@ -85,7 +85,7 @@ end ``` ## Handlers -Handlers are the code that runs in the entry point to load in addons/widgets/gadgets and distribute callins to them, and example implementation of a handler is included in the [Recoil base content](https://github.com/beyond-all-reason/RecoilEngine/blob/master/cont/base/springcontent/LuaHandler/handler.lua) for LuaIntro, LuaRules and LuaUi, and will likely do well for a simple game, although many games eventually choose to make their own handlers. This means for LuaIntro and LuaUi you can start creating widgets without worrying about handlers in the luaintro/widgets or luaui/widgets folders, and for creating gadgets for LuaRules in luarules/gadgets folder. +Handlers are the code that runs in the entry point to load in addons/widgets/gadgets and distribute callins to them, and example implementation of a handler is included in the [Recoil base content](https://github.com/beyond-all-reason/RecoilEngine/blob/master/cont/base/springcontent/LuaHandler/handler.lua) for LuaIntro, LuaRules and LuaUI, and will likely do well for a simple game, although many games eventually choose to make their own handlers. This means for LuaIntro and LuaUI you can start creating widgets without worrying about handlers in the luaintro/widgets or luaui/widgets folders, and for creating gadgets for LuaRules in luarules/gadgets folder. Below is a very simplified pseudocode example of a handler is below to illustrate adding a gadget and forwarding callins to gadgets. ```lua From 2ccc5c0d6af669f8ef525c63f8e0cd8cb4c6583e Mon Sep 17 00:00:00 2001 From: sprunk Date: Fri, 8 May 2026 02:42:44 +0200 Subject: [PATCH 36/39] Dump state handles resource packs Also now prints max storage. --- rts/System/Sync/DumpState.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/rts/System/Sync/DumpState.cpp b/rts/System/Sync/DumpState.cpp index 86ad1ea2788..0293f909105 100644 --- a/rts/System/Sync/DumpState.cpp +++ b/rts/System/Sync/DumpState.cpp @@ -687,14 +687,16 @@ void DumpState(int newMinFrameNum, int newMaxFrameNum, int newFramePeriod, std:: const CTeam* t = teamHandler.Team(a); file << "\t\tteamID: " << t->teamNum << " (controller: " << t->GetControllerName() << ")\n"; - file << "\t\t\tmetal: " << TapFloats(t->res.metal); - file << "\t\t\tenergy: " << TapFloats(t->res.energy); - file << "\t\t\tmetalPull: " << TapFloats(t->resPull.metal); - file << "\t\t\tenergyPull: " << TapFloats(t->resPull.energy); - file << "\t\t\tmetalIncome: " << TapFloats(t->resIncome.metal); - file << "\t\t\tenergyIncome: " << TapFloats(t->resIncome.energy); - file << "\t\t\tmetalExpense: " << TapFloats(t->resExpense.metal); - file << "\t\t\tenergyExpense: " << TapFloats(t->resExpense.energy); + for (const auto &[resourceID, value] : std::views::enumerate(t->res)) + file << "\t\t\tstored[" << resourceID << "]: " << TapFloats(value); + for (const auto &[resourceID, value] : std::views::enumerate(t->resStorage)) + file << "\t\t\tmaxStorage[" << resourceID << "]: " << TapFloats(value); + for (const auto &[resourceID, value] : std::views::enumerate(t->resPull)) + file << "\t\t\tpull[" << resourceID << "]: " << TapFloats(value); + for (const auto &[resourceID, value] : std::views::enumerate(t->resIncome)) + file << "\t\t\tincome[" << resourceID << "]: " << TapFloats(value); + for (const auto &[resourceID, value] : std::views::enumerate(t->resExpense)) + file << "\t\t\texpense[" << resourceID << "]: " << TapFloats(value); } #endif From 74408d1ddc1939cb8c87ec3678080817f9697053 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Fri, 10 Jul 2026 13:49:04 +0100 Subject: [PATCH 37/39] doc/site: rename 7Z_VERSION env var so mise/Tera can parse it mise 2026.7.5 (2026-07-09) tightened Tera template parsing and now rejects {{env.7Z_VERSION}} with 'Found integer but expected identifier' - Tera identifiers cannot start with a digit. Rename the env var to SEVENZIP_VERSION across the workflow and both mise configs; the value is unchanged. --- .github/workflows/publish-site.yml | 2 +- doc/site/mise.ci.toml | 2 +- doc/site/mise.toml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-site.yml b/.github/workflows/publish-site.yml index efac668ec89..94a959d9893 100644 --- a/.github/workflows/publish-site.yml +++ b/.github/workflows/publish-site.yml @@ -15,7 +15,7 @@ env: LUA_DOC_EXTRACTOR_VERSION: "3" EMMYLUA_DOC_CLI_VERSION: "0.8.2" JQ_VERSION: "1.8.0" - 7Z_VERSION: "24.09" + SEVENZIP_VERSION: "24.09" RUBY_VERSION: "3.3" MISE_ENV: "ci" jobs: diff --git a/doc/site/mise.ci.toml b/doc/site/mise.ci.toml index 50f2d98fccb..f1d504be9db 100644 --- a/doc/site/mise.ci.toml +++ b/doc/site/mise.ci.toml @@ -6,6 +6,6 @@ hugo = "{{env.HUGO_VERSION}}" "cargo:emmylua_doc_cli" = "{{env.EMMYLUA_DOC_CLI_VERSION}}" "aqua:jqlang/jq" = "{{env.JQ_VERSION}}" - "aqua:ip7z/7zip" = "{{env.7Z_VERSION}}" + "aqua:ip7z/7zip" = "{{env.SEVENZIP_VERSION}}" "npm:lua-doc-extractor" = "{{env.LUA_DOC_EXTRACTOR_VERSION}}" "ruby" = "{{env.RUBY_VERSION}}" diff --git a/doc/site/mise.toml b/doc/site/mise.toml index 8604af17fac..3eac83faf22 100644 --- a/doc/site/mise.toml +++ b/doc/site/mise.toml @@ -8,7 +8,7 @@ EMMYLUA_DOC_CLI_VERSION = "0.8.2" # lua_pages lua_library lua_check LUA_LANGUAGE_SERVER_VERSION = "3.15.0" # lua_check JQ_VERSION = "1.8.0" # binary_pages - 7Z_VERSION = "24.09" # binary_pages + SEVENZIP_VERSION = "24.09" # binary_pages RUBY_VERSION = "3.3" # lua_pages LUA_DOC_EXTRACTOR_SOURCE_REF = "https://github.com/beyond-all-reason/RecoilEngine/blob/master" RECOIL_LUA_LIBRARY_DIR = "rts/Lua/library" @@ -51,7 +51,7 @@ depends = ["latest_release_data"] description = "Generate Docs from Recoil binary" tools."aqua:jqlang/jq" = "{{env.JQ_VERSION}}" - tools."aqua:ip7z/7zip" = "{{env.7Z_VERSION}}" + tools."aqua:ip7z/7zip" = "{{env.SEVENZIP_VERSION}}" run = ''' download_url=$(jq -r '.assets[] | select(.name | contains("amd64-linux.7z")).browser_download_url' {{vars.latest_release_data}}) From 0ea0be0a5d160827d342a364eb2212a3e2d5ee40 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Fri, 10 Jul 2026 17:18:35 +0100 Subject: [PATCH 38/39] System: move float3/float4/Matrix44f str() out of line str() was defined inline in the headers, which forced onto every translation unit that includes float3.h - i.e. most of the engine, plus vendored targets (lua, luasocket) that pull it in transitively. That is a lot of compile-time cost for a debug-only helper, and it means any target touching float3.h must have the formatting library's headers available. Move the three str() bodies into their .cpp files and drop from the headers (float3.h keeps for the declaration). Now only the three math .cpp files need . Add the direct include to the two other live std::format users that were relying on the transitive pull (UnsyncedGameCommands.cpp, DumpHistory.cpp). --- rts/Game/UnsyncedGameCommands.cpp | 1 + rts/System/Matrix44f.cpp | 8 ++++++++ rts/System/Matrix44f.h | 6 +----- rts/System/Sync/DumpHistory.cpp | 2 ++ rts/System/float3.cpp | 6 ++++++ rts/System/float3.h | 6 ++---- rts/System/float4.cpp | 7 +++++++ rts/System/float4.h | 4 +--- 8 files changed, 28 insertions(+), 12 deletions(-) diff --git a/rts/Game/UnsyncedGameCommands.cpp b/rts/Game/UnsyncedGameCommands.cpp index d5b80f5f44f..c49edc0a6a7 100644 --- a/rts/Game/UnsyncedGameCommands.cpp +++ b/rts/Game/UnsyncedGameCommands.cpp @@ -1,5 +1,6 @@ /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ #include +#include #include #include diff --git a/rts/System/Matrix44f.cpp b/rts/System/Matrix44f.cpp index 6368561aca2..4c1328880dd 100644 --- a/rts/System/Matrix44f.cpp +++ b/rts/System/Matrix44f.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "System/simd_compat.h" @@ -18,6 +19,13 @@ CR_BIND(CMatrix44f, ) CR_REG_METADATA(CMatrix44f, CR_MEMBER(m)) static_assert(alignof(CMatrix44f) == 64); + +std::string CMatrix44f::str() const +{ + return std::format( + "m44(\n{:.3f} {:.3f} {:.3f} {:.3f}\n{:.3f} {:.3f} {:.3f} {:.3f}\n{:.3f} {:.3f} {:.3f} {:.3f}\n{:.3f} {:.3f} {:.3f} {:.3f})", + m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15]); +} CMatrix44f::CMatrix44f(const CMatrix44f& mat) { memcpy(&m[0], &mat.m[0], sizeof(CMatrix44f)); diff --git a/rts/System/Matrix44f.h b/rts/System/Matrix44f.h index bd5e6d1fc5b..f8061e659d7 100644 --- a/rts/System/Matrix44f.h +++ b/rts/System/Matrix44f.h @@ -176,11 +176,7 @@ class CMatrix44f float4 col[4]; }; - std::string str() const { - return std::format( - "m44(\n{:.3f} {:.3f} {:.3f} {:.3f}\n{:.3f} {:.3f} {:.3f} {:.3f}\n{:.3f} {:.3f} {:.3f} {:.3f}\n{:.3f} {:.3f} {:.3f} {:.3f})", - m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15]); - } + std::string str() const; }; diff --git a/rts/System/Sync/DumpHistory.cpp b/rts/System/Sync/DumpHistory.cpp index 8280888df87..724352a6855 100644 --- a/rts/System/Sync/DumpHistory.cpp +++ b/rts/System/Sync/DumpHistory.cpp @@ -1,5 +1,7 @@ /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ +#include + #include "DumpHistory.h" #include "SyncChecker.h" diff --git a/rts/System/float3.cpp b/rts/System/float3.cpp index 370f53e32a8..6189708d086 100644 --- a/rts/System/float3.cpp +++ b/rts/System/float3.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "System/creg/creg_cond.h" #include "System/SpringMath.h" @@ -15,6 +16,11 @@ CR_REG_METADATA(float3, (CR_MEMBER(x), CR_MEMBER(y), CR_MEMBER(z))) float float3::maxxpos = -1.0f; float float3::maxzpos = -1.0f; +std::string float3::str() const +{ + return std::format("float3({:.3f}, {:.3f}, {:.3f})", x, y, z); +} + float3 float3::PickNonParallel() const { // https://math.stackexchange.com/questions/3122010/how-to-deterministically-pick-a-vector-that-is-guaranteed-to-be-non-parallel-to diff --git a/rts/System/float3.h b/rts/System/float3.h index bf47dd90c2a..e827f2c0b73 100644 --- a/rts/System/float3.h +++ b/rts/System/float3.h @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include "System/BranchPrediction.h" #include "System/creg/creg_cond.h" @@ -838,9 +838,7 @@ class float3 static constexpr float cmp_eps() { return 1e-04f; } static constexpr float nrm_eps() { return 1e-12f; } - std::string str() const { - return std::format("float3({:.3f}, {:.3f}, {:.3f})", x, y, z); - } + std::string str() const; /** * @brief max x pos diff --git a/rts/System/float4.cpp b/rts/System/float4.cpp index 8fcdfb597b6..d6bf6971115 100644 --- a/rts/System/float4.cpp +++ b/rts/System/float4.cpp @@ -4,9 +4,16 @@ #include "System/creg/creg_cond.h" #include "System/SpringMath.h" +#include + CR_BIND(float4, ) CR_REG_METADATA(float4, (CR_MEMBER(x), CR_MEMBER(y), CR_MEMBER(z), CR_MEMBER(w))) +std::string float4::str() const +{ + return std::format("float4({:.3f}, {:.3f}, {:.3f}, {:.3f})", x, y, z, w); +} + // ensure that we use a continuous block of memory // (required for passing to gl functions) static_assert(sizeof(float4) == 4 * sizeof(float), ""); diff --git a/rts/System/float4.h b/rts/System/float4.h index 347f0d2e822..7381701184b 100644 --- a/rts/System/float4.h +++ b/rts/System/float4.h @@ -108,9 +108,7 @@ struct float4 : public float3 return (x * f.x) + (y * f.y) + (z * f.z) + (w * f.w); } - std::string str() const { - return std::format("float4({:.3f}, {:.3f}, {:.3f}, {:.3f})", x, y, z, w); - } + std::string str() const; /// Allows implicit conversion to float* (for passing to gl functions) From c5cb63af37cd8931bbbc2e1e915650c3dc3bf27a Mon Sep 17 00:00:00 2001 From: Alexander Heistermann Date: Fri, 10 Jul 2026 14:46:53 -0500 Subject: [PATCH 39/39] Remove Java AI bindings (#3059) Unused maintenance burden --- AI/Interfaces/Java/CMakeLists.txt | 661 --------- AI/Interfaces/Java/VERSION | 1 - AI/Interfaces/Java/bin/ant.properties | 21 - AI/Interfaces/Java/bin/build.xml | 258 ---- AI/Interfaces/Java/bin/jni_wrappCallback.awk | 573 -------- AI/Interfaces/Java/bin/jni_wrappCommands.awk | 192 --- AI/Interfaces/Java/bin/jni_wrappEvents.awk | 570 -------- ...native_createCallbackFPInitializations.awk | 53 - .../native_createCallbackFunctionImpls.awk | 124 -- .../Java/bin/native_wrappCallback.awk | 290 ---- .../Java/bin/native_wrappCommands.awk | 384 ----- AI/Interfaces/Java/data/InterfaceInfo.lua | 51 - AI/Interfaces/Java/data/jvm.properties | 86 -- AI/Interfaces/Java/pom.xml | 49 - .../src/main/java/com/springrts/ai/Util.java | 55 - .../Java/src/main/native/InterfaceDefines.h | 23 - .../Java/src/main/native/InterfaceExport.c | 157 --- .../Java/src/main/native/InterfaceExport.h | 61 - .../Java/src/main/native/JavaBridge.c | 1219 ---------------- .../Java/src/main/native/JavaBridge.h | 73 - AI/Interfaces/Java/src/main/native/JniUtil.c | 288 ---- AI/Interfaces/Java/src/main/native/JniUtil.h | 80 -- .../Java/src/main/native/JvmLocater.h | 53 - .../Java/src/main/native/JvmLocater_common.c | 188 --- .../Java/src/main/native/JvmLocater_common.h | 38 - .../Java/src/main/native/JvmLocater_linux.c | 266 ---- .../Java/src/main/native/JvmLocater_windows.c | 181 --- AI/Skirmish/NullJavaAI/CMakeLists.txt | 4 - AI/Skirmish/NullJavaAI/VERSION | 1 - .../NullJavaAI/bin/ant.dependent.properties | 36 - .../NullJavaAI/bin/ant.independent.properties | 29 - AI/Skirmish/NullJavaAI/bin/build.xml | 279 ---- AI/Skirmish/NullJavaAI/data/AIInfo.lua | 53 - AI/Skirmish/NullJavaAI/data/AIOptions.lua | 15 - AI/Skirmish/NullJavaAI/manifest.mf | 2 - .../NullJavaAI/src/nulljavaai/NullJavaAI.java | 187 --- AI/Skirmish/NullOOJavaAI/CMakeLists.txt | 4 - AI/Skirmish/NullOOJavaAI/VERSION | 1 - .../NullOOJavaAI/bin/ant.dependent.properties | 40 - .../bin/ant.independent.properties | 30 - AI/Skirmish/NullOOJavaAI/bin/build.xml | 342 ----- AI/Skirmish/NullOOJavaAI/data/AIInfo.lua | 53 - AI/Skirmish/NullOOJavaAI/data/AIOptions.lua | 15 - AI/Skirmish/NullOOJavaAI/manifest.mf | 2 - .../main/java/nulloojavaai/NullOOJavaAI.java | 320 ----- AI/Wrappers/JavaOO/CMakeLists.txt | 379 ----- AI/Wrappers/JavaOO/bin/ant.properties | 26 - AI/Wrappers/JavaOO/bin/build.xml | 211 --- AI/Wrappers/JavaOO/bin/wrappCallback.awk | 1241 ----------------- AI/Wrappers/JavaOO/bin/wrappEvents.awk | 627 --------- AI/Wrappers/JavaOO/jlib/vecmath-src.jar | Bin 183017 -> 0 bytes AI/Wrappers/JavaOO/jlib/vecmath.jar | Bin 310293 -> 0 bytes AI/Wrappers/JavaOO/pom.xml | 66 - .../java/com/springrts/ai/oo/AIEvent.java | 28 - .../java/com/springrts/ai/oo/AIException.java | 35 - .../java/com/springrts/ai/oo/AIFloat3.java | 118 -- .../springrts/ai/oo/CallbackAIException.java | 59 - .../com/springrts/ai/oo/EventAIException.java | 85 -- CMakeLists.txt | 7 +- rts/build/cmake/UtilJava.cmake | 101 -- 60 files changed, 1 insertion(+), 10390 deletions(-) delete mode 100644 AI/Interfaces/Java/CMakeLists.txt delete mode 100644 AI/Interfaces/Java/VERSION delete mode 100644 AI/Interfaces/Java/bin/ant.properties delete mode 100644 AI/Interfaces/Java/bin/build.xml delete mode 100755 AI/Interfaces/Java/bin/jni_wrappCallback.awk delete mode 100755 AI/Interfaces/Java/bin/jni_wrappCommands.awk delete mode 100755 AI/Interfaces/Java/bin/jni_wrappEvents.awk delete mode 100755 AI/Interfaces/Java/bin/native_createCallbackFPInitializations.awk delete mode 100755 AI/Interfaces/Java/bin/native_createCallbackFunctionImpls.awk delete mode 100755 AI/Interfaces/Java/bin/native_wrappCallback.awk delete mode 100755 AI/Interfaces/Java/bin/native_wrappCommands.awk delete mode 100644 AI/Interfaces/Java/data/InterfaceInfo.lua delete mode 100644 AI/Interfaces/Java/data/jvm.properties delete mode 100644 AI/Interfaces/Java/pom.xml delete mode 100644 AI/Interfaces/Java/src/main/java/com/springrts/ai/Util.java delete mode 100644 AI/Interfaces/Java/src/main/native/InterfaceDefines.h delete mode 100644 AI/Interfaces/Java/src/main/native/InterfaceExport.c delete mode 100644 AI/Interfaces/Java/src/main/native/InterfaceExport.h delete mode 100644 AI/Interfaces/Java/src/main/native/JavaBridge.c delete mode 100644 AI/Interfaces/Java/src/main/native/JavaBridge.h delete mode 100644 AI/Interfaces/Java/src/main/native/JniUtil.c delete mode 100644 AI/Interfaces/Java/src/main/native/JniUtil.h delete mode 100644 AI/Interfaces/Java/src/main/native/JvmLocater.h delete mode 100644 AI/Interfaces/Java/src/main/native/JvmLocater_common.c delete mode 100644 AI/Interfaces/Java/src/main/native/JvmLocater_common.h delete mode 100644 AI/Interfaces/Java/src/main/native/JvmLocater_linux.c delete mode 100644 AI/Interfaces/Java/src/main/native/JvmLocater_windows.c delete mode 100644 AI/Skirmish/NullJavaAI/CMakeLists.txt delete mode 100644 AI/Skirmish/NullJavaAI/VERSION delete mode 100644 AI/Skirmish/NullJavaAI/bin/ant.dependent.properties delete mode 100644 AI/Skirmish/NullJavaAI/bin/ant.independent.properties delete mode 100644 AI/Skirmish/NullJavaAI/bin/build.xml delete mode 100644 AI/Skirmish/NullJavaAI/data/AIInfo.lua delete mode 100644 AI/Skirmish/NullJavaAI/data/AIOptions.lua delete mode 100755 AI/Skirmish/NullJavaAI/manifest.mf delete mode 100644 AI/Skirmish/NullJavaAI/src/nulljavaai/NullJavaAI.java delete mode 100644 AI/Skirmish/NullOOJavaAI/CMakeLists.txt delete mode 100644 AI/Skirmish/NullOOJavaAI/VERSION delete mode 100644 AI/Skirmish/NullOOJavaAI/bin/ant.dependent.properties delete mode 100644 AI/Skirmish/NullOOJavaAI/bin/ant.independent.properties delete mode 100644 AI/Skirmish/NullOOJavaAI/bin/build.xml delete mode 100644 AI/Skirmish/NullOOJavaAI/data/AIInfo.lua delete mode 100644 AI/Skirmish/NullOOJavaAI/data/AIOptions.lua delete mode 100755 AI/Skirmish/NullOOJavaAI/manifest.mf delete mode 100644 AI/Skirmish/NullOOJavaAI/src/main/java/nulloojavaai/NullOOJavaAI.java delete mode 100644 AI/Wrappers/JavaOO/CMakeLists.txt delete mode 100644 AI/Wrappers/JavaOO/bin/ant.properties delete mode 100644 AI/Wrappers/JavaOO/bin/build.xml delete mode 100755 AI/Wrappers/JavaOO/bin/wrappCallback.awk delete mode 100755 AI/Wrappers/JavaOO/bin/wrappEvents.awk delete mode 100644 AI/Wrappers/JavaOO/jlib/vecmath-src.jar delete mode 100755 AI/Wrappers/JavaOO/jlib/vecmath.jar delete mode 100644 AI/Wrappers/JavaOO/pom.xml delete mode 100644 AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/AIEvent.java delete mode 100644 AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/AIException.java delete mode 100644 AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/AIFloat3.java delete mode 100644 AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/CallbackAIException.java delete mode 100644 AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/EventAIException.java delete mode 100644 rts/build/cmake/UtilJava.cmake diff --git a/AI/Interfaces/Java/CMakeLists.txt b/AI/Interfaces/Java/CMakeLists.txt deleted file mode 100644 index 9220868084e..00000000000 --- a/AI/Interfaces/Java/CMakeLists.txt +++ /dev/null @@ -1,661 +0,0 @@ -### Java AI Interface -# -# Global variables set in this file: -# * BUILD_Java_AIINTERFACE -# * Java_AIINTERFACE_VERS -# * Java_AIINTERFACE_TARGET -# -# Functions and macros defined in this file: -# * configure_java_skirmish_ai -# - -#enable_language(Java) - - -################################################################################ -### BEGIN: MACROS_AND_FUNCTIONS -# Define macros and functions to be used in this file and by Java Skirmish AIs - -# includes rts/build/cmake/UtilJava.cmake -include(UtilJava) - -# Java Skirmish AI configuration macro. -# This will be called from Java AIs at AI/Skirmish/*/CMakeLists.txt. -macro (configure_java_skirmish_ai wrapperNames) - # Assemble meta data - set(myDir "${CMAKE_CURRENT_SOURCE_DIR}") - get_last_path_part(dirName ${myDir}) - set(myName "${dirName}") - set(myJarFile "SkirmishAI") - set(myBinJarFile "${myJarFile}.jar") - set(mySrcJarFile "${myJarFile}-src.jar") - set(myJLibDir "${myDir}/data/jlib") - get_version_plus_dep_file(myVersion myVersionDepFile) - set(myTarget "${myName}") - set(myInstLibsDir "${SKIRMISH_AI_LIBS}/${myName}/${myVersion}") - set(myInstDataDir "${SKIRMISH_AI_DATA}/${myName}/${myVersion}") - # CMAKE_CURRENT_BINARY_DIR: .../spring-build-dir/AI/Skirmish/${myName} - set(myBuildDir "${CMAKE_CURRENT_BINARY_DIR}") - - # Check if the user wants to compile the AI - set(BUILD_THIS_SKIRMISHAI FALSE) - if (BUILD_Java_AIINTERFACE AND NOT myName MATCHES "${AI_EXCLUDE_REGEX}") - set(BUILD_THIS_SKIRMISHAI TRUE) - endif (BUILD_Java_AIINTERFACE AND NOT myName MATCHES "${AI_EXCLUDE_REGEX}") - if (NOT BUILD_THIS_SKIRMISHAI) - message("warning: ${myName} Skirmish AI will not be built!") - endif (NOT BUILD_THIS_SKIRMISHAI) - - # Compile and install - if (BUILD_THIS_SKIRMISHAI) - file(MAKE_DIRECTORY "${myBuildDir}/jlib") - - set(configString "default") - skirmish_ai_message(STATUS "Found Skirmish AI: ${myName} ${myVersion} (config: ${configString})") - - # Assemble project generated targets (and their libraries) we depend on - set(myDependTargets "${Java_AIINTERFACE_TARGET}") - set(myDependLibFiles "${Java_AIINTERFACE_JAR_BIN}") - foreach (wrapperName ${wrapperNames}) - set(myDependTargets ${myDependTargets} "${${wrapperName}_AIWRAPPER_TARGET}") - set(myDependLibFiles ${myDependLibFiles} "${${wrapperName}_AIWRAPPER_JAR_BIN}") - endforeach (wrapperName) - set_source_files_properties(${myDependLibFiles} PROPERTIES GENERATED TRUE) - - # find java source root - if (EXISTS "${myDir}/src/main/java") - # default Maven source dir - set(mySourceDir "${myDir}/src/main/java") - elseif (EXISTS "${myDir}/src") - # simple java source dir path - set(mySourceDir "${myDir}/src") - else (EXISTS "${myDir}/src/main/java") - message(SEND_ERROR "No sources dir found for Skirmish AI: ${myName}") - endif (EXISTS "${myDir}/src/main/java") - - # Create a list of all the AIs source files (for compiling) - file(GLOB_RECURSE mySources RELATIVE "${mySourceDir}" FOLLOW_SYMLINKS "${mySourceDir}/*.java") - # Create a list of all the AIs source files (for dependency tracking) - file(GLOB_RECURSE mySourcesDep FOLLOW_SYMLINKS "${mySourceDir}/*.java") - - set_source_files_properties("${myBuildDir}/${myBinJarFile}" PROPERTIES GENERATED TRUE) - set_source_files_properties("${myBuildDir}/${mySrcJarFile}" PROPERTIES GENERATED TRUE) - - - # If main Java package is "my.ai.pkg", this has to be set to "my". - get_first_sub_dir_name(firstSrcSubDir ${mySourceDir}) - set(myJavaPkgFirstPart "${firstSrcSubDir}") - - # Assemble additional meta data - set(myBinaryTarget "${myTarget}-BIN") - set(mySourceTarget "${myTarget}-SRC") - set(myJavaBuildDir "${myBuildDir}/classes") - - add_custom_target(${myBinaryTarget} - DEPENDS "${myBuildDir}/${myBinJarFile}") - add_dependencies(${myBinaryTarget} ${myDependTargets}) - - add_custom_target(${mySourceTarget} - DEPENDS "${myBuildDir}/${mySrcJarFile}") - - add_custom_target(${myTarget} ALL DEPENDS ${myVersionDepFile}) - add_dependencies(${myTarget} ${myBinaryTarget} ${mySourceTarget}) - - # Create our full Java class-path - create_classpath(myJavaLibs ${myJLibDir}) - concat_classpaths(myClassPath "${CLASSPATH_Java_AIINTERFACE}" "${myJavaLibs}") - foreach (wrapperName ${wrapperNames}) - concat_classpaths(myClassPath "${myClassPath}" "${${wrapperName}_AIWRAPPER_JAR_CLASSPATH}") - endforeach (wrapperName) - - # Locate the manifest file - find_manifest_file("${myDir}" myManifestFile) - if (myManifestFile) - set(myBinJarArgs "cmf" "${myManifestFile}") - else (myManifestFile) - set(myBinJarArgs "cf") - endif (myManifestFile) - - # Write list of source files to an arg-file - set(mySrcArgFile "${myBuildDir}/sourceFiles.txt") - if (EXISTS "${mySrcArgFile}") - file(REMOVE "${mySrcArgFile}") - endif (EXISTS "${mySrcArgFile}") - set(mySrcArgFile "${myBuildDir}/sourceFiles.txt") - foreach (srcFile ${mySources}) - file(APPEND "${mySrcArgFile}" "\"${srcFile}\"\n") - endforeach (srcFile) - - # Compile and pack the library - add_custom_command( - OUTPUT - "${myBuildDir}/${myBinJarFile}" - DEPENDS - ${myDependLibFiles} - ${mySourcesDep} - COMMAND "${CMAKE_COMMAND}" - "-E" "remove_directory" "${myJavaBuildDir}" - COMMAND "${CMAKE_COMMAND}" - "-E" "make_directory" "${myJavaBuildDir}" - COMMAND "${Java_JAVAC_EXECUTABLE}" - "${JAVA_COMPILE_FLAG_CONDITIONAL}" - "-Xlint:deprecation" - "-cp" "${myClassPath}" - "-d" "${myJavaBuildDir}" - "@${mySrcArgFile}" - COMMAND "${Java_JAR_EXECUTABLE}" - ${myBinJarArgs} "${myBuildDir}/${myBinJarFile}" - "-C" "${myJavaBuildDir}" "${myJavaPkgFirstPart}" - WORKING_DIRECTORY - "${mySourceDir}" - COMMENT - " ${myTarget}: Compiling sources and packing library ${myBinJarFile}" VERBATIM - ) - - # Pack the sources - add_custom_command( - OUTPUT - "${myBuildDir}/${mySrcJarFile}" - COMMAND "${Java_JAR_EXECUTABLE}" - "cf" "${myBuildDir}/${mySrcJarFile}" - "-C" "${mySourceDir}" "${myJavaPkgFirstPart}" - DEPENDS - ${mySourcesDeps} - WORKING_DIRECTORY - "${mySourceDir}" - COMMENT - " ${myTarget}: Creating sources archive ${mySrcJarFile}" VERBATIM - ) - - # Install the data files - install(DIRECTORY "${myDir}/data/" DESTINATION ${myInstDataDir}) - # Install the library - install(FILES "${myBuildDir}/${myBinJarFile}" DESTINATION ${myInstDataDir}) - # Install the sources archive (optional) - install(FILES "${myBuildDir}/${mySrcJarFile}" DESTINATION ${myInstDataDir}/jlib OPTIONAL) - - # Install files generated/downloaded during buildtime - install(DIRECTORY "${myBuildDir}/jlib/" DESTINATION ${myInstDataDir}/jlib) - if (EXISTS "${myBuildDir}/resources") - install(DIRECTORY "${myBuildDir}/resources/" DESTINATION ${myInstDataDir}/resources) - endif (EXISTS "${myBuildDir}/resources") - if (EXISTS "${myBuildDir}/config") - install(DIRECTORY "${myBuildDir}/config/" DESTINATION ${myInstDataDir}/config) - endif (EXISTS "${myBuildDir}/config") - if (EXISTS "${myBuildDir}/script") - install(DIRECTORY "${myBuildDir}/script/" DESTINATION ${myInstDataDir}/script) - endif (EXISTS "${myBuildDir}/script") - - # Install special script and config files - if (EXISTS "${myDir}/src/main/groovy") - install(DIRECTORY "${myDir}/src/main/groovy/" DESTINATION ${myInstDataDir}/script) - endif (EXISTS "${myDir}/src/main/groovy") - if (EXISTS "${myDir}/src/main/ruby") - install(DIRECTORY "${myDir}/src/main/ruby/" DESTINATION ${myInstDataDir}/script) - endif (EXISTS "${myDir}/src/main/ruby") - if (EXISTS "${myDir}/src/main/bsh") - install(DIRECTORY "${myDir}/src/main/bsh/" DESTINATION ${myInstDataDir}/script) - endif (EXISTS "${myDir}/src/main/bsh") - if (EXISTS "${myDir}/src/main/config") - install(DIRECTORY "${myDir}/src/main/config/" DESTINATION ${myInstDataDir}/config) - endif (EXISTS "${myDir}/src/main/config") - - # Install jars of wrappers - foreach (wrapperName ${wrapperNames}) - # Install the wrappers main jar - install(FILES "${${wrapperName}_AIWRAPPER_JAR_BIN}" DESTINATION ${myInstDataDir}/jlib) - set(wrapperJLibDir "${CMAKE_SOURCE_DIR}/AI/Wrappers/${wrapperName}/jlib") - # Install the wrappers java libs, if there are any - if (EXISTS ${wrapperJLibDir}) - install(DIRECTORY "${wrapperJLibDir}/" DESTINATION ${myInstDataDir}/jlib) - endif (EXISTS ${wrapperJLibDir}) - endforeach (wrapperName) - endif (BUILD_THIS_SKIRMISHAI) -endmacro (configure_java_skirmish_ai wrapperNames) - - -### END: MACROS_AND_FUNCTIONS -################################################################################ - - -set(myName "Java") -set(myDir "${CMAKE_CURRENT_SOURCE_DIR}") -set(mySourceDirRel "src/main") -set(myJavaSourceDirRel "src/main/java") -set(myNativeSourceDirRel "src/main/native") -set(myPkgFirstPart "com") -set(myPkg "${myPkgFirstPart}/springrts/ai") - - -# Check if the user wants to compile the interface -if ("${AI_TYPES}" STREQUAL "ALL" OR "${AI_TYPES}" STREQUAL "JAVA") - set(AI_TYPES_JAVA TRUE) -else ("${AI_TYPES}" STREQUAL "ALL" OR "${AI_TYPES}" STREQUAL "JAVA") - set(AI_TYPES_JAVA FALSE) -endif ("${AI_TYPES}" STREQUAL "ALL" OR "${AI_TYPES}" STREQUAL "JAVA") - -if (AI_TYPES_JAVA AND myName MATCHES "${AI_EXCLUDE_REGEX}") - set(AI_TYPES_JAVA FALSE) -endif (AI_TYPES_JAVA AND myName MATCHES "${AI_EXCLUDE_REGEX}") - - -# Look for dependencies, but only if the user wants to build the interface -if (AI_TYPES_JAVA) - if (NOT JAVA_FOUND) - find_package(Java REQUIRED COMPONENTS Development) - endif (NOT JAVA_FOUND) - if (MINGW) - set (JNI_FOUND TRUE) - else (MINGW) - # this hack is needed for FindJNI.cmake to use the JDK we want it to use, - # as otherwise it might not find one at all (eg. in the case of OpenJDK) - if ( NOT ENV{JAVA_HOME} AND JAVA_HOME ) - set(ENV{JAVA_HOME} "${JAVA_HOME}") - endif ( NOT ENV{JAVA_HOME} AND JAVA_HOME ) - find_package(JNI REQUIRED) - if (JAVA_INCLUDE_PATH) - set (JNI_FOUND TRUE) - include_directories(${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2} ${JNI_INCLUDE_DIRS}) - else (JAVA_INCLUDE_PATH) - set (JNI_FOUND FALSE) - message(WARNING "No Java includes found!") - endif (JAVA_INCLUDE_PATH) - endif (MINGW) -endif (AI_TYPES_JAVA) - - -# Check if dependencies of the interface are met -if (AI_TYPES_JAVA AND JNI_FOUND AND JAVA_FOUND AND EXISTS ${myDir} AND EXISTS ${myDir}/bin AND AWK_FOUND AND NOT myName MATCHES "${AI_EXCLUDE_REGEX}") - set_global(BUILD_${myName}_AIINTERFACE TRUE) -else (AI_TYPES_JAVA AND JNI_FOUND AND JAVA_FOUND AND EXISTS ${myDir} AND EXISTS ${myDir}/bin AND AWK_FOUND AND NOT myName MATCHES "${AI_EXCLUDE_REGEX}") - set_global(BUILD_${myName}_AIINTERFACE FALSE) - message("warning: Java AI Interface will not be built!") -endif (AI_TYPES_JAVA AND JNI_FOUND AND JAVA_FOUND AND EXISTS ${myDir} AND EXISTS ${myDir}/bin AND AWK_FOUND AND NOT myName MATCHES "${AI_EXCLUDE_REGEX}") - - -# Build -if (BUILD_${myName}_AIINTERFACE) - get_version_plus_dep_file(myVersion myVersionDepFile) - set(myTarget "${myName}-AIInterface") - set(myGenTarget "${myTarget}-generateSources") - set(myNativeTarget "${myTarget}-native") - set(myJavaTarget "${myTarget}-java") - set(myInstLibsDir ${AI_INTERFACES_LIBS}/${myName}/${myVersion}) - set(myInstDataDir ${AI_INTERFACES_DATA}/${myName}/${myVersion}) - set(myJavaSourceDirRel "${mySourceDirRel}/java") - set(myNativeSourceDirRel "${mySourceDirRel}/native") - make_absolute(mySourceDir "${myDir}" "${mySourceDirRel}") - make_absolute(myNativeSourceDir "${myDir}" "${myNativeSourceDirRel}") - make_absolute(myJavaSourceDir "${myDir}" "${myJavaSourceDirRel}") - - ai_interface_message(STATUS "Found AI Interface: ${myTarget} ${myVersion}") - - set_global(${myName}_AIINTERFACE_VERS ${myVersion}) - set_global(${myName}_AIINTERFACE_TARGET ${myTarget}) - set_global(${myName}_AIINTERFACE_TARGET_GENERATE_SOURCES ${myGenTarget}) - - - # Init some vars - # -------------- - set(myAwkScriptsDir "${myDir}/bin") - set(commonAwkScriptsDir "${CMAKE_SOURCE_DIR}/AI/Wrappers/CUtils/bin") - set(myBuildDir "${CMAKE_CURRENT_BINARY_DIR}") - set(springSourceDir "${PROJECT_SOURCE_DIR}") - set(springAIInterfaceSourceDir "${springSourceDir}/rts/ExternalAI/Interface") - set(myJavaBuildDir "${myBuildDir}/classes") - set(myJarFile "AIInterface") - set(myBinJarFile "${myJarFile}.jar") - set(mySrcJarFile "${myJarFile}-src.jar") - set(myGeneratedSourceDir "${myBuildDir}/src-generated/main") - set(myJavaGeneratedSourceDir "${myGeneratedSourceDir}/java") - set(myNativeGeneratedSourceDir "${myGeneratedSourceDir}/native") - set(myJLibDir "${myDir}/data/jlib") - create_classpath(myJavaLibs ${myJLibDir}) - set(myClassPath ".${PATH_DELIM_H}${myJavaLibs}${PATH_DELIM_H}${myJavaSourceDir}") - - # Used by Java Skirmish AIs - set_global(SOURCE_ROOT_${myName}_AIINTERFACE "${myDir}") - set_global(BUILD_ROOT_${myName}_AIINTERFACE "${myBuildDir}") - set_global(${myName}_AIINTERFACE_JAR_BIN "${myBuildDir}/${myBinJarFile}") - set_global(${myName}_AIINTERFACE_JAR_SRC "${myBuildDir}/${mySrcJarFile}") - set_global(${myName}_AIINTERFACE_POM "${myBuildDir}/pom-generated.xml") - set_global(CLASSPATH_${myName}_AIINTERFACE "${myJavaLibs}${PATH_DELIM_H}${myBuildDir}/${myBinJarFile}") - set_global(JAVA_SRC_DIR_${myName}_AIINTERFACE "${myJavaSourceDir}") - set_global(JAVA_GEN_SRC_DIR_${myName}_AIINTERFACE "${myJavaGeneratedSourceDir}") - - - # Generate sources - # ---------------- - - set(commonAwkScriptArgs - "-v" "SPRING_SOURCE_DIR=${springSourceDir}" - "-v" "INTERFACE_SOURCE_DIR=${myJavaSourceDir}" - "-v" "GENERATED_SOURCE_DIR=${myGeneratedSourceDir}" - "-v" "NATIVE_GENERATED_SOURCE_DIR=${myNativeGeneratedSourceDir}" - "-v" "JAVA_GENERATED_SOURCE_DIR=${myJavaGeneratedSourceDir}" - "-f" "${commonAwkScriptsDir}/common.awk" - "-f" "${commonAwkScriptsDir}/commonDoc.awk" - ) - - # A CMake Custom Target will always be built. - # from CMake docu: - # "add_custom_target is ALWAYS CONSIDERED OUT OF DATE" - - # Stub file for dependency tracking - set(myGeneratedSourceDirStubFile "${CMAKE_CURRENT_BINARY_DIR}/myGeneratedSourceDir.stub") - set_source_files_properties(${myGeneratedSourceDirStubFile} PROPERTIES GENERATED TRUE) - - - # source file lists (static and generated) - - set(myNativeSources - "${myNativeSourceDir}/InterfaceExport.c" - "${myNativeSourceDir}/JavaBridge.c" - "${myNativeSourceDir}/JniUtil.c" - "${myNativeSourceDir}/JvmLocater_common.c" - "${myNativeSourceDir}/JvmLocater_linux.c" - "${myNativeSourceDir}/JvmLocater_windows.c" - ) - set(myNativeGeneratedSources - "${myNativeGeneratedSourceDir}/CallbackFunctionPointerBridge.c" - "${myNativeGeneratedSourceDir}/CallbackJNIBridge.c" - "${myNativeGeneratedSourceDir}/EventsJNIBridge.c" - ) - set(myNativeGeneratedHeaders - "${myNativeGeneratedSourceDir}/CallbackFunctionPointerBridge.h" - "${myNativeGeneratedSourceDir}/CallbackJNIBridge.h" - "${myNativeGeneratedSourceDir}/EventsJNIBridge.h" - ) - - set(myJavaSources - "${myJavaSourceDir}/${myPkg}/Util.java" - ) - set(myJavaGeneratedSources - "${myJavaGeneratedSourceDir}/${myPkg}/AI.java" - "${myJavaGeneratedSourceDir}/${myPkg}/AbstractAI.java" - "${myJavaGeneratedSourceDir}/${myPkg}/AICallback.java" - "${myJavaGeneratedSourceDir}/${myPkg}/JniAICallback.java" - "${myJavaGeneratedSourceDir}/${myPkg}/Enumerations.java" - ) - - set(myGeneratedSources - ${myNativeGeneratedSources} - ${myNativeGeneratedHeaders} - ${myJavaGeneratedSources} - ) - set_source_files_properties(${myGeneratedSources} PROPERTIES GENERATED TRUE) - - - # remove all files in the generates sources dir, - # that are not generated sources (of this build) - file(GLOB_RECURSE allGeneratedSrcFiles "${myGeneratedSourceDir}/*") - foreach (genFile ${allGeneratedSrcFiles}) - list(FIND myGeneratedSources "${genFile}" isInList) - if (${isInList} EQUAL -1) - file(REMOVE "${genFile}") - endif () - endforeach (genFile) - - #cleanup generated dir if some input file changed - add_custom_command( - OUTPUT - ${myGeneratedSourceDirStubFile} - COMMAND "${CMAKE_COMMAND}" - "-E" "remove_directory" "${myGeneratedSourceDir}" - COMMAND "${CMAKE_COMMAND}" - "-E" "make_directory" "${myNativeGeneratedSourceDir}" - COMMAND "${CMAKE_COMMAND}" - "-E" "make_directory" "${myJavaGeneratedSourceDir}/${myPkg}" - COMMAND "${CMAKE_COMMAND}" - "-E" "touch" "${myGeneratedSourceDirStubFile}" - DEPENDS - "${commonAwkScriptsDir}/common.awk" - "${commonAwkScriptsDir}/commonDoc.awk" - "${myAwkScriptsDir}/native_wrappCallback.awk" - "${myAwkScriptsDir}/native_wrappCommands.awk" - "${myAwkScriptsDir}/jni_wrappCallback.awk" - "${myAwkScriptsDir}/jni_wrappCommands.awk" - "${myAwkScriptsDir}/jni_wrappEvents.awk" - "${springAIInterfaceSourceDir}/SSkirmishAICallback.h" - "${springAIInterfaceSourceDir}/AISCommands.h" - "${springAIInterfaceSourceDir}/AISEvents.h" - WORKING_DIRECTORY - "${myAwkScriptsDir}" - COMMENT - " ${myTarget}: Cleanup & create generated source directories" VERBATIM - ) - - # 1. & 2. Wrapp Callback (native->native) - add_custom_command( - OUTPUT - "${myNativeGeneratedSourceDir}/CallbackFunctionPointerBridge.h" - "${myNativeGeneratedSourceDir}/CallbackFunctionPointerBridge.c" - COMMAND "${AWK_BIN}" - ${commonAwkScriptArgs} - "-f" "${myAwkScriptsDir}/native_wrappCallback.awk" - "${springAIInterfaceSourceDir}/SSkirmishAICallback.h" - COMMAND "${AWK_BIN}" - ${commonAwkScriptArgs} - "-f" "${myAwkScriptsDir}/native_wrappCommands.awk" - "${springAIInterfaceSourceDir}/AISCommands.h" - DEPENDS - "${myGeneratedSourceDirStubFile}" - "${commonAwkScriptsDir}/common.awk" - "${commonAwkScriptsDir}/commonDoc.awk" - "${myAwkScriptsDir}/native_wrappCallback.awk" - "${myAwkScriptsDir}/native_wrappCommands.awk" - "${springAIInterfaceSourceDir}/SSkirmishAICallback.h" - "${springAIInterfaceSourceDir}/AISCommands.h" - WORKING_DIRECTORY - "${myAwkScriptsDir}" - COMMENT - " ${myTarget}: Generating native callback wrapper sources" VERBATIM - ) - - # 3. Wrapp AI Callback (native-JNI->Java) - add_custom_command( - OUTPUT - "${myNativeGeneratedSourceDir}/CallbackJNIBridge.h" - "${myNativeGeneratedSourceDir}/CallbackJNIBridge.c" - "${myJavaGeneratedSourceDir}/${myPkg}/AICallback.java" - "${myJavaGeneratedSourceDir}/${myPkg}/JniAICallback.java" - COMMAND "${AWK_BIN}" - ${commonAwkScriptArgs} - "-f" "${myAwkScriptsDir}/jni_wrappCallback.awk" - "${myNativeGeneratedSourceDir}/CallbackFunctionPointerBridge.h" - DEPENDS - "${myGeneratedSourceDirStubFile}" - "${commonAwkScriptsDir}/common.awk" - "${commonAwkScriptsDir}/commonDoc.awk" - "${myAwkScriptsDir}/jni_wrappCallback.awk" - "${myNativeGeneratedSourceDir}/CallbackFunctionPointerBridge.h" - WORKING_DIRECTORY - "${myAwkScriptsDir}" - COMMENT - " ${myTarget}: Generating JNI callback wrapper sources" VERBATIM - ) - - # 4. Wrapp AI Events (native-JNI->Java) - add_custom_command( - OUTPUT - "${myNativeGeneratedSourceDir}/EventsJNIBridge.h" - "${myNativeGeneratedSourceDir}/EventsJNIBridge.c" - "${myJavaGeneratedSourceDir}/${myPkg}/AI.java" - "${myJavaGeneratedSourceDir}/${myPkg}/AbstractAI.java" - COMMAND "${AWK_BIN}" - ${commonAwkScriptArgs} - "-f" "${myAwkScriptsDir}/jni_wrappEvents.awk" - "${springAIInterfaceSourceDir}/AISEvents.h" - DEPENDS - "${myGeneratedSourceDirStubFile}" - "${commonAwkScriptsDir}/common.awk" - "${commonAwkScriptsDir}/commonDoc.awk" - "${myAwkScriptsDir}/jni_wrappEvents.awk" - "${springAIInterfaceSourceDir}/AISEvents.h" - WORKING_DIRECTORY - "${myAwkScriptsDir}" - COMMENT - " ${myTarget}: Generating JNI events wrapper sources" VERBATIM - ) - # 5. Wrapp ENUMS - - add_custom_command( - OUTPUT - "${myJavaGeneratedSourceDir}/${myPkg}/Enumerations.java" - COMMAND "${AWK_BIN}" - ${commonAwkScriptArgs} - "-f" "${myAwkScriptsDir}/jni_wrappCommands.awk" - "${springAIInterfaceSourceDir}/AISCommands.h" - DEPENDS - "${myGeneratedSourceDirStubFile}" - "${commonAwkScriptsDir}/common.awk" - "${commonAwkScriptsDir}/commonDoc.awk" - "${myAwkScriptsDir}/jni_wrappCommands.awk" - "${springAIInterfaceSourceDir}/AISCommands.h" - WORKING_DIRECTORY - "${myAwkScriptsDir}" - COMMENT - " ${myTarget}: Generating enums wrapper sources" VERBATIM - ) - add_custom_target(${myGenTarget} DEPENDS ${myGeneratedSources}) - add_dependencies(generateSources ${myGenTarget}) - - # Locate the manifest file - find_manifest_file("${myDir}" myManifestFile) - if (myManifestFile) - set(myBinJarArgs "cmf" "${myManifestFile}") - else (myManifestFile) - set(myBinJarArgs "cf") - endif (myManifestFile) - - # Build the native part - # --------------------- - if (MINGW) - # It is important that this is used instead of the one - # from the installed JDK, as the jni_md.h is in here too, - # and this file contains OS (win32) specific information. - include_directories(BEFORE ${MINGWLIBS}/include/java) - endif (MINGW) - include_directories(BEFORE "${rts}/lib/streflop" "${myNativeSourceDir}" "${myNativeGeneratedSourceDir}") - add_library(${myNativeTarget} MODULE ${myNativeSources} ${myNativeGeneratedSources} ${ai_common_SRC} ${myVersionDepFile}) - add_dependencies(${myNativeTarget} generateVersionFiles) - target_link_libraries(${myNativeTarget} CUtils streflop) - set_target_properties(${myNativeTarget} PROPERTIES COMPILE_FLAGS "-DUSING_STREFLOP") - set_target_properties(${myNativeTarget} PROPERTIES OUTPUT_NAME "AIInterface") - fix_lib_name(${myNativeTarget}) - - - # Build the java part - # ------------------- - - # Write list of source files to an arg-file - set(mySrcArgFile "${myBuildDir}/sourceFiles.txt") - if (EXISTS "${mySrcArgFile}") - file(REMOVE "${mySrcArgFile}") - endif (EXISTS "${mySrcArgFile}") - foreach (srcFile ${myJavaSources} ${myJavaGeneratedSources}) - file(APPEND "${mySrcArgFile}" "\"${srcFile}\"\n") - endforeach (srcFile) - - add_custom_command( - OUTPUT - "${myBuildDir}/${myBinJarFile}" - COMMAND "${CMAKE_COMMAND}" - "-E" "remove_directory" "${myJavaBuildDir}" - COMMAND "${CMAKE_COMMAND}" - "-E" "make_directory" "${myJavaBuildDir}" - COMMAND "${Java_JAVAC_EXECUTABLE}" ARGS - "${JAVA_COMPILE_FLAG_CONDITIONAL}" - "-Xlint:deprecation" - "-cp" "${myClassPath}" - "-d" "${myJavaBuildDir}" - "@${mySrcArgFile}" - COMMAND "${Java_JAR_EXECUTABLE}" ARGS - ${myBinJarArgs} "${myBuildDir}/${myBinJarFile}" - "-C" "${myJavaBuildDir}" "${myPkgFirstPart}" - DEPENDS - ${myJavaSources} - # Using the native target reduces parallelism slightly - # (less then 1s per build => negliable), but prevetns multiple - # execution of the source generating custom-commands - # (which results in wrongly generated source files with "make -j ") - ${myJavaGeneratedSources} - ${myNativeTarget} - WORKING_DIRECTORY - "${myJavaGeneratedSourceDir}" - COMMENT - " ${myTarget}: Compiling sources and packing library ${myBinJarFile}" VERBATIM - ) - - add_custom_command( - OUTPUT - "${myBuildDir}/${mySrcJarFile}" - COMMAND "${Java_JAR_EXECUTABLE}" ARGS - "cf" "${myBuildDir}/${mySrcJarFile}" - "-C" "${myJavaSourceDir}" "${myPkgFirstPart}" - COMMAND "${Java_JAR_EXECUTABLE}" ARGS - "uf" "${myBuildDir}/${mySrcJarFile}" - "-C" "${myJavaGeneratedSourceDir}" "${myPkgFirstPart}" - COMMAND "${Java_JAR_EXECUTABLE}" ARGS - "uf" "${myBuildDir}/${mySrcJarFile}" - "-C" "${mySourceDir}" "native" - COMMAND "${Java_JAR_EXECUTABLE}" ARGS - "uf" "${myBuildDir}/${mySrcJarFile}" - "-C" "${myGeneratedSourceDir}" "native" - COMMAND "${Java_JAR_EXECUTABLE}" ARGS - "uf" "${myBuildDir}/${mySrcJarFile}" - "-C" "${myDir}" "VERSION" - DEPENDS - ${myJavaSources} - # Using the native target reduces parallelism slightly - # (less then 1s per build => negliable), but prevetns multiple - # execution of the source generating custom-commands - # (which results in wrongly generated source files with "make -j ") - ${myJavaGeneratedSources} - ${myNativeTarget} - WORKING_DIRECTORY - "${myBuildDir}" - COMMENT - " ${myTarget}: Creating sources archive ${mySrcJarFile}" VERBATIM - ) - - add_custom_target(${myJavaTarget} - DEPENDS - "${myBuildDir}/${myBinJarFile}" - "${myBuildDir}/${mySrcJarFile}" - ) - - add_custom_target(${myTarget} ALL) - add_dependencies(${myTarget} ${myNativeTarget}) - - # this sets the version in pom.xml - set(myMavenProperties "-Dmy.version=${myVersion}") - add_custom_command( - OUTPUT - "${myBuildDir}/pom-generated.xml" - COMMAND "${CMAKE_COMMAND}" - "-Dfile.in=${myDir}/pom.xml" - "-Dfile.out=${myBuildDir}/pom-generated.xml" - ${myMavenProperties} - "-P" "${CMAKE_MODULES_SPRING}/ConfigureFile.cmake" - DEPENDS - "${myDir}/pom.xml" - WORKING_DIRECTORY - "${myDir}" - COMMENT - " ${myTarget}: Configure pom.xml" VERBATIM - ) - set_source_files_properties("${myBuildDir}/pom-generated.xml" PROPERTIES GENERATED TRUE) - - add_dependencies(${myTarget} ${myJavaTarget}) - - # Install the native library - install(TARGETS ${myNativeTarget} DESTINATION ${myInstLibsDir}) - # Install the data files - install(DIRECTORY "${myDir}/data/" DESTINATION ${myInstLibsDir} FILES_MATCHING PATTERN REGEX "InterfaceInfo\\.lua$") - install(DIRECTORY "${myDir}/data/" DESTINATION ${myInstDataDir} FILES_MATCHING PATTERN REGEX "InterfaceInfo\\.lua$" EXCLUDE PATTERN "*") - # Install the library - install(FILES "${myBuildDir}/${myBinJarFile}" DESTINATION ${myInstDataDir}) - # Install the sources archive - install(FILES "${myBuildDir}/${mySrcJarFile}" DESTINATION ${myInstDataDir}/jlib) -endif (BUILD_${myName}_AIINTERFACE) diff --git a/AI/Interfaces/Java/VERSION b/AI/Interfaces/Java/VERSION deleted file mode 100644 index ceab6e11ece..00000000000 --- a/AI/Interfaces/Java/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.1 \ No newline at end of file diff --git a/AI/Interfaces/Java/bin/ant.properties b/AI/Interfaces/Java/bin/ant.properties deleted file mode 100644 index b9c0a35d3ec..00000000000 --- a/AI/Interfaces/Java/bin/ant.properties +++ /dev/null @@ -1,21 +0,0 @@ -# Paths are relative to the project home (which is ../ from this file). -# All values are optional. - -;spring.home=../../.. - -# This is used only in the next property -;build.home=${spring.home}/build -# Where jar files will be built -;build.dir=${build.home}/AI/Interfaces/${interface.name} - -# Where generated sources shall be created in -;src.generated=${build.dir}/src-generated/main -;src.generated.native=${src.generated}/native -;src.generated.java=${src.generated}/java - -# This is used only in the next property -;dist.home=${spring.home}/dist -# Where jar files will be installed to -;dist.dir=${dist.home}/AI/Interfaces/${interface.name}/${interface.version} -# Where HTML JavaDoc files will be generated -;doc.dir=${dist.home}/AI/Interfaces/${interface.name}/${interface.version}/doc/jdoc diff --git a/AI/Interfaces/Java/bin/build.xml b/AI/Interfaces/Java/bin/build.xml deleted file mode 100644 index 86e0033068a..00000000000 --- a/AI/Interfaces/Java/bin/build.xml +++ /dev/null @@ -1,258 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AI/Interfaces/Java/bin/jni_wrappCallback.awk b/AI/Interfaces/Java/bin/jni_wrappCallback.awk deleted file mode 100755 index 833bdb90ec3..00000000000 --- a/AI/Interfaces/Java/bin/jni_wrappCallback.awk +++ /dev/null @@ -1,573 +0,0 @@ -#!/usr/bin/awk -f -# -# This awk script creates a Java class with native/JNI functions, -# plus their respective native counterparts, -# to call to C functions in: -# NATIVE_GENERATED_SOURCE_DIR/CallbackFunctionPointerBridge.h -# -# This script uses functions from the following files: -# * common.awk -# * commonDoc.awk -# Variables that can be set on the command-line (with -v): -# * GENERATED_SOURCE_DIR: the generated sources root dir -# -# usage: -# awk -f thisScript.awk -f common.awk -f commonDoc.awk -# awk -f thisScript.awk -f common.awk -f commonDoc.awk \ -# -v 'GENERATED_SOURCE_DIR=/tmp/build/AI/Interfaces/Java/src-generated/main' -# - -BEGIN { - # initialize things - - # define the field splitter(-regex) - FS = "(,)|(\\()|(\\);)"; - - # Used by other scripts - JAVA_MODE = 1; - - # These vars can be assigned externally, see file header. - # Set the default values if they were not supplied on the command line. - if (!GENERATED_SOURCE_DIR) { - GENERATED_SOURCE_DIR = "../src-generated/main"; - } - if (!JAVA_GENERATED_SOURCE_DIR) { - JAVA_GENERATED_SOURCE_DIR = GENERATED_SOURCE_DIR "/java"; - } - if (!NATIVE_GENERATED_SOURCE_DIR) { - NATIVE_GENERATED_SOURCE_DIR = GENERATED_SOURCE_DIR "/native"; - } - - nativeBridge = "CallbackFunctionPointerBridge"; - bridgePrefix = "bridged__"; - - jniBridge = "CallbackJNIBridge"; - - myPkgA = "com.springrts.ai"; - myPkgD = convertJavaNameFormAToD(myPkgA); - myPkgC = convertJavaNameFormAToC(myPkgA); - myInterface = "AICallback"; - myClass = "JniAICallback"; - - fi = 0; -} - -function doWrapp(funcIndex_dw) { - - paramListJava_dw = funcParamList[funcIndex_dw]; - doWrapp_dw = 1; - - fullName_dw = funcFullName[funcIndex_dw]; - if (doWrapp_dw) { - metaInf_dw = funcMetaInf[funcIndex_dw]; - - if (match(metaInf_dw, /ARRAY:/)) { - #doWrapp_dw = 0; - } - if (match(metaInf_dw, /MAP:/)) { - #doWrapp_dw = 0; - } - if (match(fullName_dw, "^" bridgePrefix "File_")) { - doWrapp_dw = 0; - } - } else { - print("Java-AIInterface: NOTE: JNI level: Callback: intentionally not wrapped: " fullName_dw); - } - - return doWrapp_dw; -} - -function createNativeFileName(fileName_fn, isHeader_fn) { - - absFileName_fn = NATIVE_GENERATED_SOURCE_DIR "/" fileName_fn; - if (isHeader_fn) { - absFileName_fn = absFileName_fn ".h"; - } else { - absFileName_fn = absFileName_fn ".c"; - } - - return absFileName_fn; -} - -function printNativeJNI() { - - outFile_nh = createNativeFileName(jniBridge, 1); - outFile_nc = createNativeFileName(jniBridge, 0); - - printCommentsHeader(outFile_nh); - printCommentsHeader(outFile_nc); - - print("") >> outFile_nh; - print("#ifndef __CALLBACK_JNI_BRIDGE_H") >> outFile_nh; - print("#define __CALLBACK_JNI_BRIDGE_H") >> outFile_nh; - print("") >> outFile_nh; - print("#include ") >> outFile_nh; - print("") >> outFile_nh; - print("#ifdef __cplusplus") >> outFile_nh; - print("extern \"C\" {") >> outFile_nh; - print("#endif") >> outFile_nh; - print("") >> outFile_nh; - - print("") >> outFile_nc; - print("#include \"" jniBridge ".h\"") >> outFile_nc; - print("") >> outFile_nc; - print("#include \"" nativeBridge ".h\"") >> outFile_nc; - print("") >> outFile_nc; - print("") >> outFile_nc; - - # print the wrapping functions - for (i=0; i < fi; i++) { - fullName = funcFullName[i]; - retType = funcRetTypeC[i]; - paramList = funcParamListC[i]; - paramListNoTypes = removeParamTypes(paramList); - metaInf = funcMetaInf[i]; - - if (doWrapp(i)) { - isRetString = part_isRetString(fullName, metaInf); - if (isRetString) { - retString = part_getRetString(metaInf); - split(retString, retStringNames, ":"); # must return 2 elements - } - - javaName = fullName; - sub("^" bridgePrefix, "", javaName); - gsub(/_/, "_1", javaName); - jni_funcName = "Java_" myPkgC "_" myClass "_" javaName; - jni_retType = convertCToJNIType(retType); - jni_paramList = ""; - size_params = split(paramList, params, ","); - for (p=1; p <= size_params; p++) { - pType_c = params[p]; - sub(/ [^ ]+$/, "", pType_c); - pType_c = trim(pType_c); - - pName = params[p]; - sub(/^.* /, "", pName); - - pType_jni = convertCToJNIType(pType_c, pName); - - # these are later used for conversion - c_paramTypes[p] = pType_c; - c_paramNames[p] = pName; - jni_paramTypes[p] = pType_jni; - jni_paramNames[p] = pName; - - if (isRetString && (pName == retStringNames[1] || pName == retStringNames[2])) { - if (pName == retStringNames[1]) - jni_retType = convertCToJNIType(pType_c); - continue; - } - jni_paramList = jni_paramList ", " pType_jni " " pName; - } - jni_paramListNoTypes = removeParamTypes(jni_paramList); - isVoidRet = match(jni_retType, /^void$/); - - # print function declaration to *.h - #printFunctionComment_Common(outFile_nh, funcDocComment, i, ""); - print("JNIEXPORT " jni_retType " JNICALL " jni_funcName "(JNIEnv* __env, jobject __obj" jni_paramList ");") >> outFile_nh; - print("") >> outFile_nh; - - # print function definition to *.c - print("JNIEXPORT " jni_retType " JNICALL " jni_funcName "(JNIEnv* __env, jobject __obj" jni_paramList ") {") >> outFile_nc; - print("") >> outFile_nc; - - if (!isVoidRet) { - print("\t" jni_retType " _ret;") >> outFile_nc; - print("") >> outFile_nc; - } - - # Return value conversion - pre call - retType_isString = ((match(retType, /^(const )?char*/) || isRetString) && (jni_retType == "jstring")); - retTypeConv = 0; - if (retType_isString) { - if (isRetString) - print("\t" "char " retStringNames[1] "[2048] = {'\\0'};") >> outFile_nc; - print("\t" retType " _retNative;") >> outFile_nc; - retTypeConv = 1; - } - - hasRetParam = 0; - # Params conversion - pre call - for (p=1; p <= size_params; p++) { - pType_jni = jni_paramTypes[p]; - - if (pType_jni == "jstring") { - # jstring - if (!isRetString || c_paramNames[p] != retStringNames[1]) { - c_paramNames[p] = c_paramNames[p] "_native"; - sub(" " jni_paramNames[p], " " c_paramNames[p], paramListNoTypes); - print("\t" c_paramTypes[p] " " c_paramNames[p] " = (" c_paramTypes[p] ") (*__env)->GetStringUTFChars(__env, " jni_paramNames[p] ", NULL);") >> outFile_nc; - } - } else if (pType_jni == "jint") { - if (isRetString && c_paramNames[p] == retStringNames[2]) - sub(" " jni_paramNames[p], " sizeof(" retStringNames[1] ")", paramListNoTypes); - } else if (match(pType_jni, /^j.+Array$/)) { - # primitive array - c_paramNames[p] = c_paramNames[p] "_native"; - sub(" " jni_paramNames[p], " " c_paramNames[p], paramListNoTypes); - - capArrType = pType_jni; - sub(/^j/, "", capArrType); - sub(/Array$/, "", capArrType); - capArrType = capitalize(capArrType); - - _isPrimitive = (capArrType != "Object"); - _isString = !_isPrimitive && match(c_paramTypes[p], /(const )?char\*\*/); - - print("\t" c_paramTypes[p] " " c_paramNames[p] " = NULL;") >> outFile_nc; - print("\t" "if (" jni_paramNames[p] " != NULL) {") >> outFile_nc; - if (_isPrimitive) { - print("\t\t" c_paramNames[p] " = (" c_paramTypes[p] ") (*__env)->Get" capArrType "ArrayElements(__env, " jni_paramNames[p] ", NULL);") >> outFile_nc; - } else if (_isString) { - print("\t\t" "const int " c_paramNames[p] "_size = (int) (*__env)->GetArrayLength(__env, " jni_paramNames[p] ");") >> outFile_nc; - print("\t\t" c_paramNames[p] " = (" c_paramTypes[p] ") malloc(sizeof(char*) * " c_paramNames[p] "_size);") >> outFile_nc; - } else { - print("ERROR: do not know how to convert parameter type: " pType_jni); - exit(1); - } - print("\t" "}") >> outFile_nc; - } else if (pType_jni == "jobject") { - # StringBuffer - hasRetParam = 1; - cPaNa = c_paramNames[p]; - c_paramNames[p] = cPaNa "_native"; - sub(" " jni_paramNames[p], " " c_paramNames[p], paramListNoTypes); - print("\t" "char " c_paramNames[p] "[MAX_RESPONSE_SIZE];") >> outFile_nc; - retParamConversion = "\t" "jclass clazz = (*__env)->GetObjectClass(__env, " cPaNa ");" "\n"; - retParamConversion = retParamConversion "\t" "jmethodID mid = (*__env)->GetMethodID(__env, clazz, \"append\", \"(Ljava/lang/String;)Ljava/lang/StringBuffer;\");" "\n" - retParamConversion = retParamConversion "\t" "jstring " cPaNa "_jStr = (*__env)->NewStringUTF(__env, " c_paramNames[p] ");" "\n"; - retParamConversion = retParamConversion "\t" "(*__env)->CallObjectMethod(__env, " cPaNa ", mid, " cPaNa "_jStr);" - } - } - - condRet = ""; - if (!isVoidRet) { - if (retTypeConv) { - condRet = "_retNative = "; - } else { - condRet = "_ret = (" jni_retType ") "; - } - } - print("\t" condRet fullName "(" paramListNoTypes ");") >> outFile_nc; - if (hasRetParam) { - print(retParamConversion) >> outFile_nc; - } - - # Params conversion - post call - for (p=1; p <= size_params; p++) { - pType_jni = jni_paramTypes[p]; - - if (pType_jni == "jstring") { - # jstring - if (!isRetString || c_paramNames[p] != retStringNames[1]) - print("\t" "(*__env)->ReleaseStringUTFChars(__env, " jni_paramNames[p] ", " c_paramNames[p] ");") >> outFile_nc; - } else if (match(pType_jni, /^j.+Array$/)) { - # primitive array - capArrType = pType_jni; - sub(/^j/, "", capArrType); - sub(/Array$/, "", capArrType); - capArrType = capitalize(capArrType); - - _isPrimitive = (capArrType != "Object"); - _isString = !_isPrimitive && match(c_paramTypes[p], /(const )?char\*\*/); - - print("\t" "if (" jni_paramNames[p] " != NULL) {") >> outFile_nc; - if (_isPrimitive) { - _elementJNativeType = jni_paramTypes[p]; - sub(/Array$/, "", _elementJNativeType); # jfloatArray -> jfloat - print("\t\t" "(*__env)->Release" capArrType "ArrayElements(__env, " jni_paramNames[p] ", (" _elementJNativeType "*) " c_paramNames[p] ", 0 /* copy back changes and release */);") >> outFile_nc; - } else if (_isString) { - print("\t\t" "const int " c_paramNames[p] "_size = (int) (*__env)->GetArrayLength(__env, " jni_paramNames[p] ");") >> outFile_nc; - print("\t\t" "int " c_paramNames[p] "_i;") >> outFile_nc; - print("\t\t" "jstring " c_paramNames[p] "_jStr;") >> outFile_nc; - print("\t\t" "for (" c_paramNames[p] "_i=0; " c_paramNames[p] "_i < " c_paramNames[p] "_size; ++" c_paramNames[p] "_i) {") >> outFile_nc; - print("\t\t\t" c_paramNames[p] "_jStr = (jstring) (*__env)->NewStringUTF(__env, " c_paramNames[p] "[" c_paramNames[p] "_i]);") >> outFile_nc; - print("\t\t\t" "(*__env)->SetObjectArrayElement(__env, " jni_paramNames[p] ", " c_paramNames[p] "_i, " c_paramNames[p] "_jStr);") >> outFile_nc; - print("\t\t\t" "(*__env)->DeleteLocalRef(__env, " c_paramNames[p] "_jStr);") >> outFile_nc; - print("\t\t" "}") >> outFile_nc; - print("\t\t" "free(" c_paramNames[p] ");") >> outFile_nc; - } - print("\t" "}") >> outFile_nc; - } else if (pType_jni == "jobject" ) { - # StringBuffer - print("\t" "(*__env)->DeleteLocalRef(__env, " cPaNa "_jStr);") >> outFile_nc; - } - } - - # Return value conversion - post call - if (retType_isString) { - print("\t" "_ret = (*__env)->NewStringUTF(__env, " (isRetString ? retStringNames[1] : "_retNative") ");") >> outFile_nc; - } - - if (!isVoidRet) { - print("") >> outFile_nc; - print("\t" "return _ret;") >> outFile_nc; - } - print("" "}") >> outFile_nc; - print("") >> outFile_nc; - } else { - print("Note: The following function is intentionally not wrapped: " fullName); - } - } - - - print("#ifdef __cplusplus") >> outFile_nh; - print("} // extern \"C\"") >> outFile_nh; - print("#endif") >> outFile_nh; - print("") >> outFile_nh; - print("#endif // __CALLBACK_JNI_BRIDGE_H") >> outFile_nh; - print("") >> outFile_nh; - - close(outFile_nh); - close(outFile_nc); -} - - -function printHeader(outFile_h, javaPkg_h, javaClassName_h) { - - printCommentsHeader(outFile_h); - print("") >> outFile_h; - print("package " javaPkg_h ";") >> outFile_h; - print("") >> outFile_h; - print("") >> outFile_h; - print("/**") >> outFile_h; - print(" * Lets Java Skirmish AIs call back to the Spring engine.") >> outFile_h; - print(" * We are using JNI for best speed.") >> outFile_h; - print(" *") >> outFile_h; - print(" * @author AWK wrapper script") >> outFile_h; - print(" * @version GENERATED") >> outFile_h; - print(" */") >> outFile_h; - if (javaClassName_h == myClass) { - print("public class " javaClassName_h " implements " myInterface " {") >> outFile_h; - } else { - print("public interface " javaClassName_h " {") >> outFile_h; - } - print("") >> outFile_h; -} - -function createJavaFileName(clsName_f) { - return JAVA_GENERATED_SOURCE_DIR "/" myPkgD "/" clsName_f ".java"; -} - -function printJavaClsAndInt() { - - outFile_i = createJavaFileName(myInterface); - outFile_c = createJavaFileName(myClass); - - printHeader(outFile_i, myPkgA, myInterface); - printHeader(outFile_c, myPkgA, myClass); - - # print the static registrator - print("\tstatic {") >> outFile_c; - print("\t\tSystem.loadLibrary(\"AIInterface\");") >> outFile_c; - print("\t}") >> outFile_c; - print("") >> outFile_c; - - # print skirmishAIId getter in interface - print("\t" "public int SkirmishAI_getSkirmishAIId();") >> outFile_i; - print("") >> outFile_i; - - # print skirmishAIId member, constructor and getter - print("\t" "private int skirmishAIId;") >> outFile_c; - print("") >> outFile_c; - print("\t" "public " myClass "(int skirmishAIId) {") >> outFile_c; - print("\t\t" "this.skirmishAIId = skirmishAIId;") >> outFile_c; - print("\t}") >> outFile_c; - print("") >> outFile_c; - print("\t" "@Override") >> outFile_c; - print("\t" "public int SkirmishAI_getSkirmishAIId() {") >> outFile_c; - print("\t\t" "return this.skirmishAIId;") >> outFile_c; - print("\t}") >> outFile_c; - print("") >> outFile_c; - - # print the callback methods - for (i=0; i < fi; i++) { - if (doWrapp(i)) { - fullName = funcFullName[i]; - retType = funcRetTypeJ[i]; - paramList = funcParamListJ[i]; - metaInf = funcMetaInf[i]; - - isRetString = part_isRetString(fullName, metaInf); - if (isRetString) { - retString = part_getRetString(metaInf); - split(retString, retStringNames, ":"); # must return 2 elements - size_params = split(paramList, params, ","); - for (p=1; p <= size_params; p++) { - pName = params[p]; - sub(/^.* /, "", pName); - if (pName == retStringNames[1]) { - ps = 1; - } else if (pName == retStringNames[2]) { - ps = 2; - } else continue; - pType_c = params[p]; - sub(/ [^ ]+$/, "", pType_c); - pType_c = trim(pType_c); - retStringTypes[ps] = convertJNIToJavaType(convertCToJNIType(pType_c, pName)); - } - retType = retStringTypes[1]; - sub(", " retStringTypes[1] " " retStringNames[1] ", " retStringTypes[2] " " retStringNames[2], "", paramList); - } - - paramListNoSID = paramList; - sub(/int _skirmishAIId(, )?/, "", paramListNoSID); - paramListNoSIDNoTypes = removeParamTypes(paramListNoSID); - if (paramListNoSIDNoTypes != "") { - paramListNoSIDNoTypes = ", " paramListNoSIDNoTypes; - } - condRet = ""; - if (retType != "void") { - condRet = "return "; - } - - sub("^" bridgePrefix, "", fullName); - - metaInfCommand = ""; - if (metaInf != "") { - metaInfCommand = " // " metaInf; - } - - # print the interface function - printFunctionComment_Common(outFile_i, funcDocComment, i, "\t"); - print("\t" "public " retType " " fullName "(" paramListNoSID ");" metaInfCommand) >> outFile_i; - print("") >> outFile_i; - - # print the interface implementing function - commentText = getFunctionComment_Common(funcDocComment, i); - if (match(commentText, /@deprecated/)) { - # this prevents javac from outputting a warning - print("\t" "/** @deprecated */") >> outFile_c; - } - print("\t" "@Override") >> outFile_c; - print("\t" "public " retType " " fullName "(" paramListNoSID ") {") >> outFile_c; - print("\t\t" condRet "this." fullName "(this.skirmishAIId" paramListNoSIDNoTypes ");") >> outFile_c; - print("\t" "}") >> outFile_c; - - # print the private native function - print("\t" "private native " retType " " fullName "(" paramList ");") >> outFile_c; - print("") >> outFile_c; - } - } - - print("}") >> outFile_i; - print("") >> outFile_i; - close(outFile_i); - - print("}") >> outFile_c; - print("") >> outFile_c; - close(outFile_c); -} - - -function wrappFunction(funcDef, commentEol) { - - doParse = 1; - - if (doParse) { - size_funcParts = split(funcDef, funcParts, "(,)|(\\()|(\\))"); - # because the empty part after ");" would count as part as well - size_funcParts--; - retType_c = trim(funcParts[2]); - retType_j = convertJNIToJavaType(convertCToJNIType(retType_c)); - fullName = trim(funcParts[3]); - - # function parameters - paramList_c = ""; - paramList_j = ""; - paramList = ""; - - for (i=4; i<=size_funcParts && !match(funcParts[i], /.*\/\/.*/); i++) { - type_c = extractParamType(funcParts[i]); - type_c = cleanupCType(type_c); - name = extractParamName(funcParts[i]); - type_j = convertJNIToJavaType(convertCToJNIType(type_c, name)); - if (i == 4) { - cond_comma = ""; - } else { - cond_comma = ", "; - } - paramList_c = paramList_c cond_comma type_c " " name; - paramList_j = paramList_j cond_comma type_j " " name; - } - - funcFullName[fi] = fullName; - funcRetTypeC[fi] = retType_c; - funcRetTypeJ[fi] = retType_j; - funcParamListC[fi] = paramList_c; - funcParamListJ[fi] = paramList_j; - funcMetaInf[fi] = trim(commentEol); - storeDocLines(funcDocComment, fi); - fi++; - } else { - print("warning: function intentionally NOT wrapped: " funcDef); - } -} - - - -# This function has to return true (1) if a doc comment (eg: /** foo bar */) -# can be deleted. -# If there is no special condition you want to apply, -# it should always return true (1), -# cause there are additional mechanism to prevent accidental deleting. -# see: commonDoc.awk -function canDeleteDocumentation() { - return isMultiLineFunc != 1; -} - - - -# save function pointer info into arrays -# ... 2nd, 3rd, ... line of a function pointer definition -{ - if (isMultiLineFunc) { # function is defined on one single line - funcIntermLine = $0; - # separate possible comment at end of line: // fu bar - commentEol = funcIntermLine; - if (sub(/.*\/\//, "", commentEol)) { - commentEolTot = commentEolTot commentEol; - } - # remove possible comment at end of line: // fu bar - sub(/[ \t]*\/\/.*/, "", funcIntermLine); - funcIntermLine = trim(funcIntermLine); - funcSoFar = funcSoFar " " funcIntermLine; - if (match(funcSoFar, /;$/)) { - # function ends in this line - wrappFunction(funcSoFar, commentEolTot); - isMultiLineFunc = 0; - } - } -} -# 1st line of a function pointer definition -/^EXPORT\(/ { - - funcStartLine = $0; - # separate possible comment at end of line: // foo bar - commentEolTot = ""; - commentEol = funcStartLine; - if (sub(/.*\/\//, "", commentEol)) { - commentEolTot = commentEolTot commentEol; - } - # remove possible comment at end of line: // fu bar - sub(/\/\/.*$/, "", funcStartLine); - funcStartLine = trim(funcStartLine); - if (match(funcStartLine, /;$/)) { - # function ends in this line - wrappFunction(funcStartLine, commentEolTot); - } else { - funcSoFar = funcStartLine; - isMultiLineFunc = 1; - } -} - - - - -END { - # finalize things - - printNativeJNI(); - printJavaClsAndInt(); -} diff --git a/AI/Interfaces/Java/bin/jni_wrappCommands.awk b/AI/Interfaces/Java/bin/jni_wrappCommands.awk deleted file mode 100755 index abd4c0e62e1..00000000000 --- a/AI/Interfaces/Java/bin/jni_wrappCommands.awk +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/awk -f -# -# This awk script creates a Java class containing important enumerations. -# These enumerations are taken from: rts/ExternalAI/Interface/AISCommands.h -# It currently only takes CommandTopic and UnitCommandOptions enumerations -# -# This script uses functions from the following files: -# * common.awk -# * commonDoc.awk -# Variables that can be set on the command-line (with -v): -# * GENERATED_SOURCE_DIR: the generated sources root dir -# -# usage: -# awk -f thisScript.awk -f common.awk -f commonDoc.awk -# awk -f thisScript.awk -f common.awk -f commonDoc.awk \ -# -v 'GENERATED_SOURCE_DIR=/tmp/build/AI/Interfaces/Java/src-generated/main' -# - -BEGIN { - # initialize things - - # define the field splitter(-regex) - FS = "[ \t]+"; - - # Used by other scripts - JAVA_MODE = 1; - - # These vars can be assigned externally, see file header. - # Set the default values if they were not supplied on the command line. - if (!GENERATED_SOURCE_DIR) { - GENERATED_SOURCE_DIR = "../src-generated/main"; - } - if (!JAVA_GENERATED_SOURCE_DIR) { - JAVA_GENERATED_SOURCE_DIR = GENERATED_SOURCE_DIR "/java"; - } - if (!NATIVE_GENERATED_SOURCE_DIR) { - NATIVE_GENERATED_SOURCE_DIR = GENERATED_SOURCE_DIR "/native"; - } - - myPkgA = "com.springrts.ai"; - myPkgD = convertJavaNameFormAToD(myPkgA); - myClass = "Enumerations"; - - #create empty arrays, holding names and values of the two enumerations - cmdsTopicNamesLength = 0; - split("", cmdsTopicNames); - cmdsTopicValuesLength = 0; - split("", cmdsTopicValues); - unitCmdsTopicNamesLength = 0; - split("", unitCmdsTopicNames); - unitCmdsTopicValuesLength = 0; - split("", unitCmdsTopicValues); -} - - -function createJavaFileName(fileName_fn) { - return JAVA_GENERATED_SOURCE_DIR "/" myPkgD "/" fileName_fn ".java"; -} - -function printGeneralJavaHeader(outFile_h, javaPkg_h, javaClassName_h) { - - printCommentsHeader(outFile_h); - print("") >> outFile_h; - print("package " javaPkg_h ";") >> outFile_h; - print("") >> outFile_h; - print("") >> outFile_h; - print("/**") >> outFile_h; - print(" * These are the Java exposed enumerations.") >> outFile_h; - print(" * We are not calling the engine, this is a pure Java class.") >> outFile_h; - print(" *") >> outFile_h; - print(" * @author AWK wrapper script") >> outFile_h; - print(" * @version GENERATED") >> outFile_h; - print(" */") >> outFile_h; - print("public abstract class " javaClassName_h " {") >> outFile_h; - print("") >> outFile_h; -} - -function printJavaHeader() { - - outFile_i = createJavaFileName(myClass); - - printGeneralJavaHeader(outFile_i, myPkgA, myClass); -} - -function printJavaEnums(enums) { - printJavaEnum("CommandTopic", cmdsTopicNames, cmdsTopicNamesLength, cmdsTopicValues); - print("") >> outFile_i; - printJavaEnum("UnitCommandOptions", unitCmdsTopicNames, unitCmdsTopicNamesLength, unitCmdsTopicValues); -} - -function printJavaEnum(enumName, names, namesLength, values) { - outFile_i = createJavaFileName(myClass); - printEnumHeader(enumName); - - # Prints the enum members and values - first = 0; - for (i=0; i> outFile_i; - } else { - printf(",\n\t\t") >> outFile_i; - } - printJavaEnumItem(names[i], values[i]); - } - - if (first != 0) { - print(";\n") >> outFile_i; - } - printEnumEnd(enumName); -} - -function printEnumHeader(name) { - outFile_i = createJavaFileName(myClass); - - # Prints the enum start - print("\tpublic enum " name " {") >> outFile_i; -} - -function printEnumEnd(name) { - outFile_i = createJavaFileName(myClass); - - # Prints the enum end - print("\t\tprivate final int id;") >> outFile_i; - print("\t\t" name "(int id) { this.id = id; }") >> outFile_i; - print("\t\tpublic int getValue() { return id; }") >> outFile_i; - print("\t}") >> outFile_i; -} - -function printJavaEnumItem(key, value) { - printf(key "(" value ")") >> outFile_i; -} - -function printJavaEnd() { - - outFile_i = createJavaFileName(myClass); - - print("}") >> outFile_i; - print("") >> outFile_i; - - close(outFile_i); -} - -/^[ \t]*COMMAND_.*$/ { - - doWrapp = !match(($0), /.*COMMAND_NULL.*/); - if (doWrapp) { - #parse enumeration of format x = number1, - sub(",", "", $4); - cmdsTopicNames[cmdsTopicNamesLength] = $2; - cmdsTopicNamesLength++; - cmdsTopicValues[cmdsTopicValuesLength] = $4; - cmdsTopicValuesLength++; - } else { - print("Java-AIInterface: NOTE: JNI level: COMMANDS: intentionally not wrapped: " $2); - } -} - -/^[ \t]*UNIT_COMMAND_.*$/ { - doWrapp = !match(($0), /.*COMMAND_NULL.*/); - if (doWrapp) { - unitCmdsTopicNames[unitCmdsTopicNamesLength] = $2; - unitCmdsTopicNamesLength++; - #parse enumeration of format x = (number1 << number2), - sub("[ \t]*//.*", "", $0); - sub(",", "", $0); - sub($3, "", $0); - sub($2, "", $0); - sub("[ \t]*", "", $0); - unitCmdsTopicValues[unitCmdsTopicValuesLength] = $0; - unitCmdsTopicValuesLength++; - } else { - print("Java-AIInterface: NOTE: JNI level: UNIT_COMMANDS: intentionally not wrapped: " $2); - } -} - -# This function has to return true (1) if a doc comment (eg: /** foo bar */) -# can be deleted. -# If there is no special condition you want to apply, -# it should always return true (1), -# cause there are additional mechanism to prevent accidental deleting. -# see: commonDoc.awk -function canDeleteDocumentation() { - return isInsideEvtStruct != 1; -} - -END { - # finalize things - printJavaHeader(); - printJavaEnums(); - printJavaEnd(); -} diff --git a/AI/Interfaces/Java/bin/jni_wrappEvents.awk b/AI/Interfaces/Java/bin/jni_wrappEvents.awk deleted file mode 100755 index a1907084b30..00000000000 --- a/AI/Interfaces/Java/bin/jni_wrappEvents.awk +++ /dev/null @@ -1,570 +0,0 @@ -#!/usr/bin/awk -f -# -# This awk script creates a Java class with one function per event, -# plus a C wrapper to easily/comfortably call these functions from C. -# input is taken from file: -# rts/ExternalAI/Interface/AISEvents.h -# -# This script uses functions from the following files: -# * common.awk -# * commonDoc.awk -# Variables that can be set on the command-line (with -v): -# * GENERATED_SOURCE_DIR: the generated sources root dir -# -# usage: -# awk -f thisScript.awk -f common.awk -f commonDoc.awk -# awk -f thisScript.awk -f common.awk -f commonDoc.awk \ -# -v 'GENERATED_SOURCE_DIR=/tmp/build/AI/Interfaces/Java/src-generated/main' -# - -BEGIN { - # initialize things - - # define the field splitter(-regex) - FS = "[ \t]+"; - - # Used by other scripts - JAVA_MODE = 1; - - # These vars can be assigned externally, see file header. - # Set the default values if they were not supplied on the command line. - if (!GENERATED_SOURCE_DIR) { - GENERATED_SOURCE_DIR = "../src-generated/main"; - } - if (!JAVA_GENERATED_SOURCE_DIR) { - JAVA_GENERATED_SOURCE_DIR = GENERATED_SOURCE_DIR "/java"; - } - if (!NATIVE_GENERATED_SOURCE_DIR) { - NATIVE_GENERATED_SOURCE_DIR = GENERATED_SOURCE_DIR "/native"; - } - - myWrapperName = "EventsJNIBridge"; - myWrapperPrefix = "eventsJniBridge_"; - - myPkgA = "com.springrts.ai"; - myPkgD = convertJavaNameFormAToD(myPkgA); - myAIClass = "AI"; - myAIAbstractClass = "AbstractAI"; - myAICallbackInt = "AICallback"; - - ind_evtTopics = 0; - ind_evtStructs = 0; - isInsideEvtStruct = 0; -} - - - -# Checks if a field is available and is no comment -function isFieldUsable(f) { - - valid = 0; - - if (f && !match(f, /.*\/\/.*/)) { - valid = 1; - } - - return valid; -} - -function createNativeFileName(fileName_fn, isHeader_fn) { - - absFileName_fn = NATIVE_GENERATED_SOURCE_DIR "/" fileName_fn; - if (isHeader_fn) { - absFileName_fn = absFileName_fn ".h"; - } else { - absFileName_fn = absFileName_fn ".c"; - } - - return absFileName_fn; -} -function createJavaFileName(fileName_fn) { - return JAVA_GENERATED_SOURCE_DIR "/" myPkgD "/" fileName_fn ".java"; -} - - -function printNativeHeader() { - - outFile_nh = createNativeFileName(myWrapperName, 1); - outFile_nc = createNativeFileName(myWrapperName, 0); - printCommentsHeader(outFile_nh); - printCommentsHeader(outFile_nc); - - # print includes (header) - print("") >> outFile_nh; - print("#ifndef __EVENTS_JNI_BRIDGE_H") >> outFile_nh; - print("#define __EVENTS_JNI_BRIDGE_H") >> outFile_nh; - print("") >> outFile_nh; - print("#include ") >> outFile_nh; - print("") >> outFile_nh; - print("#ifdef __cplusplus") >> outFile_nh; - print("extern \"C\" {") >> outFile_nh; - print("#endif") >> outFile_nh; - print("") >> outFile_nh; - - # print includes (source) - print("") >> outFile_nc; - print("#include \"" myWrapperName ".h\"") >> outFile_nc; - print("") >> outFile_nc; - print("#include \"JavaBridge.h\" // for INT_AI") >> outFile_nc; - print("#include \"JniUtil.h\"") >> outFile_nc; - print("#include \"ExternalAI/Interface/AISEvents.h\"") >> outFile_nc; - print("#include // for calloc(), free()") >> outFile_nc; - print("") >> outFile_nc; - print("") >> outFile_nc; - - # print global vars (source) - print("size_t skirmishAIId_size = 0;") >> outFile_nc; - print("jobject* skirmishAIId_callback = NULL;") >> outFile_nc; - print("") >> outFile_nc; -} - -function printNativeEventMethodVars() { - - for (e=0; e < ind_evtStructs; e++) { - printNativeEventMethodVar(e); - } - print("") >> outFile_nc; -} -function printNativeEventMethodVar(evtIndex) { - - outFile_nc = createNativeFileName(myWrapperName, 0); - - eName = evtsName[evtIndex]; - eNameLowerized = lowerize(eName); - - print("jmethodID m_ai_" eNameLowerized " = NULL;") >> outFile_nc; -} - -function printNativeStaticInitFuncHead() { - - # ... header - print("/**") >> outFile_nh; - print(" * Initialized stuff needed for sending events to an AI library instance.") >> outFile_nh; - print(" *") >> outFile_nh; - print(" * @author hoijui") >> outFile_nh; - print(" * @version GENERATED") >> outFile_nh; - print(" */") >> outFile_nh; - print("int " myWrapperPrefix "initStatic(JNIEnv* env, size_t skirmishAIId_size);") >> outFile_nh; - print("") >> outFile_nh; - # ... source - print("int " myWrapperPrefix "initStatic(JNIEnv* env, size_t _skirmishAIId_size) {") >> outFile_nc; - print("") >> outFile_nc; - print("\t" "skirmishAIId_size = _skirmishAIId_size;") >> outFile_nc; - print("\t" "skirmishAIId_callback = (jobject*) calloc(skirmishAIId_size, sizeof(jobject));") >> outFile_nc; - print("\t" "size_t t;") >> outFile_nc; - print("\t" "for (t=0; t < skirmishAIId_size; ++t) {") >> outFile_nc; - print("\t\t" "skirmishAIId_callback[t] = NULL;") >> outFile_nc; - print("\t" "}") >> outFile_nc; - print("") >> outFile_nc; - print("\t" "jobject c_aiInt = (*env)->FindClass(env, \"" myPkgD "/" myAIClass "\");") >> outFile_nc; - print("\t" "if (jniUtil_checkException(env, \"Failed fetching AI interface class " myPkgA "." myAIClass "\")) { return -2; }") >> outFile_nc; - print("") >> outFile_nc; -} - -function printNativeEventStaticInits() { - - for (e=0; e < ind_evtStructs; e++) { - printNativeEventStaticInit(e); - } - print("") >> outFile_nc; -} -function printNativeEventStaticInit(evtIndex) { - - outFile_nc = createNativeFileName(myWrapperName, 0); - - eName = evtsName[evtIndex]; - eNameLowerized = lowerize(eName); - eSignature = "("; - for (m=0; m < evtsNumMembers[evtIndex]; m++) { - type_c = evtsMembers_type_c[evtIndex, m]; - type_jni = convertCToJNIType(type_c); - type_sig = convertJNIToSignatureType(type_jni); - eSignature = eSignature type_sig; - } - eSignature = eSignature ")I"; - if (eNameLowerized == "init") { - eSignature = "(IL" myPkgD "/" myAICallbackInt ";)I"; - } - - print("\t" "m_ai_" eNameLowerized " = jniUtil_getMethodID(env, c_aiInt, \"" eNameLowerized "\", \"" eSignature "\");") >> outFile_nc; - print("\t" "if (jniUtil_checkException(env, \"Failed fetching Java AI method ID for: " eNameLowerized "\")) { return -3; }") >> outFile_nc; - print("") >> outFile_nc; -} - -function printNativeInitFunc() { - - print("\t" "return 0; // -> no error") >> outFile_nc; - print("}") >> outFile_nc; - print("") >> outFile_nc; - - # ... header - print("/**") >> outFile_nh; - print(" * Initialized stuff needed for an AI instance.") >> outFile_nh; - print(" *") >> outFile_nh; - print(" * @author hoijui") >> outFile_nh; - print(" * @version GENERATED") >> outFile_nh; - print(" */") >> outFile_nh; - print("int " myWrapperPrefix "initAI(JNIEnv* env, int skirmishAIId, jobject callback);") >> outFile_nh; - print("") >> outFile_nh; - # ... source - print("int " myWrapperPrefix "initAI(JNIEnv* env, int skirmishAIId, jobject callback) {") >> outFile_nc; - print("") >> outFile_nc; - print("\t" "int res = -1;") >> outFile_nc; - print("") >> outFile_nc; - print("\t" "skirmishAIId_callback[skirmishAIId] = callback;") >> outFile_nc; - print("\t" "res = 0;") >> outFile_nc; - print("") >> outFile_nc; - print("\t" "return res;") >> outFile_nc; - print("}") >> outFile_nc; - print("") >> outFile_nc; -} - -function printNativeHandleFuncHead() { - - # ... header - print("/**") >> outFile_nh; - print(" * For documentation, see SSkirmishAILibrary::handleEvent() in:") >> outFile_nh; - print(" * ExternalAI/Interface/SSkirmishAILibrary.h") >> outFile_nh; - print(" *") >> outFile_nh; - print(" * @author hoijui") >> outFile_nh; - print(" * @version GENERATED") >> outFile_nh; - print(" */") >> outFile_nh; - print("int " myWrapperPrefix "handleEvent(JNIEnv* env, jobject aiInstance, int skirmishAIId, int topic, const void* data);") >> outFile_nh; - print("") >> outFile_nh; - # ... source - print("int " myWrapperPrefix "handleEvent(JNIEnv* env, jobject aiInstance, int skirmishAIId, int topic, const void* data) {") >> outFile_nc; - print("") >> outFile_nc; - print("\t" "int _ret = -1;") >> outFile_nc; - print("") >> outFile_nc; -# print("\t" "jobject o_ai = skirmishAIId_aiObject[skirmishAIId];") >> outFile_nc; -# print("\t" "//assert(o_ai != NULL);") >> outFile_nc; -# print("") >> outFile_nc; - - # only add this for debugging purposes -# print("\t" "// in case we missed handling an exception elsewhere") >> outFile_nc; -# print("\t" "// and it is therefore still pending, we handle it here") >> outFile_nc; -# print("\t" "if ((*env)->ExceptionCheck(env)) {") >> outFile_nc; -# print("\t\t" "(*env)->ExceptionDescribe(env);") >> outFile_nc; -# print("\t\t" "_ret = -6;") >> outFile_nc; -# print("\t\t" "return _ret;") >> outFile_nc; -# print("\t" "}") >> outFile_nc; -# print("") >> outFile_nc; - - print("\t" "switch (topic) {") >> outFile_nc; -} - - -function printNativeEventCases() { - - for (e=0; e < ind_evtStructs; e++) { - printNativeEventCase(e); - } -} -function printNativeEventCase(evtIndex) { - - outFile_nc = createNativeFileName(myWrapperName, 0); - - topicName = evtsTopicName[evtIndex]; - topicValue = evtsTopicNameValue[topicName]; - eName = evtsName[evtIndex]; - eNameLowerized = lowerize(eName); - eStruct = "S" eName "Event"; - - print("\t\t" "case " topicName ": {") >> outFile_nc; - print("\t\t\t" "const struct " eStruct "* evt = (const struct "eStruct"*) data;") >> outFile_nc; - - paramsEvt = ""; - conversion_pre = ""; - for (m=0; m < evtsNumMembers[evtIndex]; m++) { - type_c = evtsMembers_type_c[evtIndex, m]; - name_c = evtsMembers_name[evtIndex, m]; - value_c = "evt->" name_c; - name_jni = value_c; - - type_jni = convertCToJNIType(type_c); - if (type_jni == "jstring") { - name_jni = name_c "_jni"; - conversion_pre = conversion_pre "\n\t\t\t" type_jni " " name_jni " = (*env)->NewStringUTF(env, " value_c ");"; - } else if (type_jni == "jfloatArray") { - name_jni = name_c "_jni"; - - array_size = "sizeof(" value_c ")"; - if (match(name_c, /_posF3$/)) { - array_size = "3"; - } - - conversion_pre = conversion_pre "\n\t\t\t" type_jni " " name_jni " = (*env)->NewFloatArray(env, " array_size ");"; - conversion_pre = conversion_pre "\n\t\t\t" "(*env)->SetFloatArrayRegion(env, " name_jni ", 0, " array_size ", " value_c ");"; - } else if (type_jni == "jintArray") { - name_jni = name_c "_jni"; - - array_size = "sizeof(" value_c ")"; - - conversion_pre = conversion_pre "\n\t\t\t" type_jni " " name_jni " = (*env)->NewIntArray(env, " array_size ");"; - conversion_pre = conversion_pre "\n\t\t\t" "(*env)->SetIntArrayRegion(env, " name_jni ", 0, " array_size ", " value_c ");"; - } - - paramsEvt = paramsEvt ", " name_jni; - } - sub(/^, /, "", paramsEvt); - if (eNameLowerized == "init") { - sub(/evt->callback/, "skirmishAIId_callback[evt->skirmishAIId]", paramsEvt); - } - - print(conversion_pre) >> outFile_nc; - print("\t\t\t" "_ret = (*env)->CallIntMethod(env, aiInstance, m_ai_" eNameLowerized ", " paramsEvt ");") >> outFile_nc; - - print("\t\t\t" "break;") >> outFile_nc; - print("\t\t" "}") >> outFile_nc; -} - -function printNativeEnd() { - - outFile_nh = createNativeFileName(myWrapperName, 1); - outFile_nc = createNativeFileName(myWrapperName, 0); - - print("#ifdef __cplusplus") >> outFile_nh; - print("} // extern \"C\"") >> outFile_nh; - print("#endif") >> outFile_nh; - print("") >> outFile_nh; - print("#endif // __EVENTS_JNI_BRIDGE_H") >> outFile_nh; - print("") >> outFile_nh; - - print("\t\t" "default: {") >> outFile_nc; - print("\t\t\t" "_ret = -4;") >> outFile_nc; - print("\t\t\t" "break;") >> outFile_nc; - print("\t\t" "}") >> outFile_nc; - print("\t" "}") >> outFile_nc; - print("") >> outFile_nc; - print("\t" "if ((*env)->ExceptionCheck(env)) {") >> outFile_nc; - print("\t\t" "(*env)->ExceptionDescribe(env);") >> outFile_nc; - print("\t\t" "_ret = -5;") >> outFile_nc; - print("\t" "}") >> outFile_nc; - print("") >> outFile_nc; - print("\t" "return _ret;") >> outFile_nc; - print("}") >> outFile_nc; - - close(outFile_nh); - close(outFile_nc); -} - - -function printGeneralJavaHeader(outFile_h, javaPkg_h, javaClassName_h) { - - printCommentsHeader(outFile_h); - print("") >> outFile_h; - print("package " javaPkg_h ";") >> outFile_h; - print("") >> outFile_h; - print("") >> outFile_h; - print("/**") >> outFile_h; - print(" * This is the Java entry point from events coming from the engine.") >> outFile_h; - print(" * We are using JNI for best in speed.") >> outFile_h; - print(" *") >> outFile_h; - print(" * @author AWK wrapper script") >> outFile_h; - print(" * @version GENERATED") >> outFile_h; - print(" */") >> outFile_h; - if (javaClassName_h == myAIAbstractClass) { - print("public abstract class " javaClassName_h " implements " myAIClass " {") >> outFile_h; - } else { - print("public interface " javaClassName_h " {") >> outFile_h; - } - print("") >> outFile_h; -} - -function printJavaHeader() { - - outFile_i = createJavaFileName(myAIClass); - outFile_a = createJavaFileName(myAIAbstractClass); - - printGeneralJavaHeader(outFile_i, myPkgA, myAIClass); - printGeneralJavaHeader(outFile_a, myPkgA, myAIAbstractClass); -} - -function printJavaEvents() { - - for (e=0; e < ind_evtStructs; e++) { - printJavaEvent(e); - } -} -function printJavaEvent(evtIndex) { - - outFile_i = createJavaFileName(myAIClass); - outFile_a = createJavaFileName(myAIAbstractClass); - - topicName = evtsTopicName[evtIndex]; - topicValue = evtsTopicNameValue[topicName]; - eName = evtsName[evtIndex]; - eMetaComment = evtsMetaComment[evtIndex]; - eNameLowerized = lowerize(eName); - eCls = eName "AIEvent"; - - # Add the member comments to the main comment as @param attributes - numEvtDocLines = evtsDocComment[evtIndex, "*"]; - for (m=firstMember; m < evtsNumMembers[evtIndex]; m++) { - numLines = evtMbrsDocComments[evtIndex*1000 + m, "*"]; - if (numLines > 0) { - evtsDocComment[evtIndex, numEvtDocLines] = "@param " evtsMembers_name[evtIndex, m]; - } - for (l=0; l < numLines; l++) { - if (l == 0) { - _preDocLine = "@param " evtsMembers_name[evtIndex, m] " "; - } else { - _preDocLine = " " lengtAsSpaces(evtsMembers_name[evtIndex, m]) " "; - } - evtsDocComment[evtIndex, numEvtDocLines] = _preDocLine evtMbrsDocComments[evtIndex*1000 + m, l]; - numEvtDocLines++; - } - } - evtsDocComment[evtIndex, "*"] = numEvtDocLines; - - paramsTypes = ""; - for (m=0; m < evtsNumMembers[evtIndex]; m++) { - name_c = evtsMembers_name[evtIndex, m]; - type_c = evtsMembers_type_c[evtIndex, m]; - type_java = convertJNIToJavaType(convertCToJNIType(type_c)); - - paramsTypes = paramsTypes ", " type_java " " name_c; - } - sub(/^, /, "", paramsTypes); - if (eNameLowerized == "init") { - paramsTypes = "int skirmishAIId, AICallback callback"; - } - - _condMetaComments = eMetaComment; - if (_condMetaComments != "") { - _condMetaComments = " // " _condMetaComments; - } - - print("") >> outFile_i; - printFunctionComment_Common(outFile_i, evtsDocComment, evtIndex, "\t"); - print("\t" "public int " eNameLowerized "(" paramsTypes ");" _condMetaComments) >> outFile_i; - - print("") >> outFile_a; - print("\t" "@Override") >> outFile_a; - print("\t" "public int " eNameLowerized "(" paramsTypes ") {") >> outFile_a; - print("") >> outFile_a; - print("\t\t" "// signal: event handled OK") >> outFile_a; - print("\t\t" "return 0;") >> outFile_a; - print("\t" "}") >> outFile_a; -} - -function printJavaEnd() { - - outFile_i = createJavaFileName(myAIClass); - outFile_a = createJavaFileName(myAIAbstractClass); - - print("}") >> outFile_i; - print("") >> outFile_i; - - print("}") >> outFile_a; - print("") >> outFile_a; - - close(outFile_i); - close(outFile_a); -} - - -function saveMember(ind_mem_s, member_s) { - - name_s = extractParamName(member_s); - type_c_s = extractCType(member_s); - - evtsMembers_name[ind_evtStructs, ind_mem_s] = name_s; - evtsMembers_type_c[ind_evtStructs, ind_mem_s] = type_c_s; -} - - -# aggare te los event defines in order -/^[ \t]*EVENT_.*$/ { - - doWrapp = !match(($0), /.*EVENT_NULL.*/) && !match(($0), /.*EVENT_TO_ID_ENGINE.*/); - if (doWrapp) { - sub(",", "", $4); - evtsTopicNameValue[$2] = $4; - } else { - print("Java-AIInterface: NOTE: JNI level: Events: intentionally not wrapped: " $2); - } -} - - -# This function has to return true (1) if a doc comment (eg: /** foo bar */) -# can be deleted. -# If there is no special condition you want to apply, -# it should always return true (1), -# cause there are additional mechanism to prevent accidental deleting. -# see: commonDoc.awk -function canDeleteDocumentation() { - return isInsideEvtStruct != 1; -} - -################################################################################ -### BEGIN: parsing and saving the event structs - -# end of struct S*Event -/^}; \/\/\$ EVENT_/ { - - evtsNumMembers[ind_evtStructs] = ind_evtMember; - evtsTopicName[ind_evtStructs] = $3; - _metaComment = $0; - sub("^.*" evtsTopicName[ind_evtStructs], "", _metaComment); - evtsMetaComment[ind_evtStructs] = trim(_metaComment); - - ind_evtStructs++; - isInsideEvtStruct = 0; -} - - -# inside of struct S*Event -{ - if (isInsideEvtStruct == 1) { - size_tmpMembers = split($0, tmpMembers, ";"); - # cause there is an empty part behind the ';' - size_tmpMembers--; - for (i=1; i<=size_tmpMembers; i++) { - tmpMembers[i] = trim(tmpMembers[i]); - if (tmpMembers[i] == "" || match(tmpMembers[i], /^\/\//)) { - break; - } - # This would bork with more then 1000 members in an event, - # or more then 1000 events - storeDocLines(evtMbrsDocComments, ind_evtStructs*1000 + ind_evtMember); - saveMember(ind_evtMember, tmpMembers[i]); - ind_evtMember++; - } - } -} - -# beginning of struct S*Event -/^struct S.*Event( \{)?/ { - - isInsideEvtStruct = 1; - ind_evtMember = 0; - eventName = $2; - sub(/^S/, "", eventName); - sub(/Event$/, "", eventName); - - evtsName[ind_evtStructs] = eventName; - storeDocLines(evtsDocComment, ind_evtStructs); -} - -### END: parsing and saving the event structs -################################################################################ - - - - -END { - # finalize things - printNativeHeader(); - printNativeEventMethodVars(); - printNativeStaticInitFuncHead(); - printNativeEventStaticInits(); - printNativeInitFunc(); - printNativeHandleFuncHead(); - printNativeEventCases(); - printNativeEnd(); - - printJavaHeader(); - printJavaEvents(); - printJavaEnd(); -} diff --git a/AI/Interfaces/Java/bin/native_createCallbackFPInitializations.awk b/AI/Interfaces/Java/bin/native_createCallbackFPInitializations.awk deleted file mode 100755 index c6aed9bdeef..00000000000 --- a/AI/Interfaces/Java/bin/native_createCallbackFPInitializations.awk +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/awk -f -# -# This awk script creates the function pointer initializations -# for the C callbacks; eg: -# rts/ExternalAI/Interface/SSkirmishAICallback.h -# rts/ExternalAI/Interface/SAIInterfaceCallback.h -# - -BEGIN { - # initialize things - - # define the field splitter(-regex) - #FS="[ \t]+" -} - - -# Some utility functions - -function ltrim(s) { sub(/^[ \t]+/, "", s); return s; } -function rtrim(s) { sub(/[ \t]+$/, "", s); return s; } -function trim(s) { return rtrim(ltrim(s)); } - - -function printInit(functionPointerName) { - - functionName = functionPointerName; - sub(/Clb_/, "skirmishAiCallback_", functionName); - - print(" callback->" functionPointerName " = &" functionName ";") -} - - -/^.*CALLING_CONV.*$/ { - - line = trim($0); - doWrapp = !match(line, /^[ \t]*\/\/.*/); - if (doWrapp) { - numParts = split(line, parts, /\(CALLING_CONV \*/); - if (numParts == 2) { - numParts2 = split(parts[2], parts2, /\)\(/); - if (numParts2 == 2) { - fn = parts2[1]; - printInit(fn); - } - } - } -} - - - -END { - # finalize things -} diff --git a/AI/Interfaces/Java/bin/native_createCallbackFunctionImpls.awk b/AI/Interfaces/Java/bin/native_createCallbackFunctionImpls.awk deleted file mode 100755 index c9a789ab005..00000000000 --- a/AI/Interfaces/Java/bin/native_createCallbackFunctionImpls.awk +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/awk -f -# -# This awk script creates function impls that can be used in the C callbacks -# with some little adjustments; eg: -# rts/ExternalAI/SSkirmishAICallbackImpl.cpp -# rts/ExternalAI/SAIInterfaceCallbackImpl.cpp -# -# Accepts input like this: -# [code] -# bool requireSonarUnderWater; -# -# // construction related fields -# /// Should constructions without builders decay? -# bool constructionDecay; -# [/code] -# -# use like this: -# awk -f yourScript.awk -f common.awk -f commonDoc.awk [additional-params] -# this should work with all flavours of AWK (eg. gawk, mawk, nawk, ...) -# - -BEGIN { - # initialize things - - # define the field splitter(-regex) - #FS="[ \t]+" - - # 0 -> print impl - # 1 -> print impl header - # 2 -> print interface header - printingHeader = 1; -} - -function getCType(gct_type) { - - if (match(gct_type, /string/)) { - return "const char* const"; - } else { - return gct_type; - } -} -function getToCTypeConv(gtctc_type) { - - if (match(gtctc_type, /string/)) { - return ".c_str()"; - } else { - return ""; - } -} -function getFuncPrefix(gfp_type) { - - #if (match(gfp_type, /bool/)) { - # return "is"; - #} else { - return "get"; - #} -} - -function printFunc(pf_fieldName, pf_type, pf_header) { - - pf_cType = getCType(pf_type); - pf_toCTypeConv = getToCTypeConv(pf_type); - pf_funcPrefix = getFuncPrefix(pf_type); - - if (pf_header == 2 && hasStoredDoc()) { - printStoredDoc(""); - } - - if (pf_header != 0) { - pf_firstLineEnd = ";"; - pf_sizeToFill = length("const char* const") - length(pf_cType); - if (pf_sizeToFill < 0) { - pf_sizeToFill = 0; - } - pf_filler = ""; - for (pf_i = 0; pf_i < pf_sizeToFill; pf_i++) { - pf_filler = pf_filler " "; - } - } else { - pf_firstLineEnd = " {"; - pf_filler = ""; - } - - if (pf_header == 2) { - print(pf_cType pf_filler " (CALLING_CONV *PreFixName_" pf_funcPrefix capitalize(pf_fieldName) ")(int teamId)" pf_firstLineEnd); - } else { - print("EXPORT(" pf_cType pf_filler ") PreFixName_" pf_funcPrefix capitalize(pf_fieldName) "(int teamId)" pf_firstLineEnd); - } - if (!pf_header) { - print("\t" "return modInfo->" pf_fieldName pf_toCTypeConv ";"); - print("}"); - } -} - -# needed by commonDoc.awk -function canDeleteDocumentation() { - return 1; -} - - -{ - line = trim($0); - - if (line == "") { - print(line); - } else if (!isInsideDoc()) { - sub(/\;$/, "", line); - - name = line; - sub(/^.*[ \t]/, "", name); - - cppType = line; - sub(name, "", cppType); - cppType = rtrim(cppType); - - printFunc(name, cppType, printingHeader); - } -} - - - -END { - # finalize things -} diff --git a/AI/Interfaces/Java/bin/native_wrappCallback.awk b/AI/Interfaces/Java/bin/native_wrappCallback.awk deleted file mode 100755 index b920c196a6e..00000000000 --- a/AI/Interfaces/Java/bin/native_wrappCallback.awk +++ /dev/null @@ -1,290 +0,0 @@ -#!/usr/bin/awk -f -# -# This awk script creates C functions which call C function pointers in: -# rts/ExternalAI/Interface/SSkirmishAICallback.h -# -# Right after running this script, you have to wrap the native command structs -# into functions. -# -# This script uses functions from the following files: -# * common.awk -# * commonDoc.awk -# Variables that can be set on the command-line (with -v): -# * GENERATED_SOURCE_DIR the root generated sources dir -# default: "../src-generated/main" -# * NATIVE_GENERATED_SOURCE_DIR the native generated sources dir -# default: GENERATED_SOURCE_DIR + "/native" -# -# usage: -# awk -f thisScript.awk -f common.awk -f commonDoc.awk -# awk -f thisScript.awk -f common.awk -f commonDoc.awk \ -# -v 'NATIVE_GENERATED_SOURCE_DIR=/tmp/build/AI/Interfaces/Java/src-generated/main/native' -# - -BEGIN { - # initialize things - - # define the field splitter(-regex) - FS = "(,)|(\\()|(\\);)"; - - # These vars can be assigned externally, see file header. - # Set the default values if they were not supplied on the command line. - if (!GENERATED_SOURCE_DIR) { - GENERATED_SOURCE_DIR = "../src-generated/main"; - } - if (!NATIVE_GENERATED_SOURCE_DIR) { - NATIVE_GENERATED_SOURCE_DIR = GENERATED_SOURCE_DIR "/native"; - } - - nativeBridge = "CallbackFunctionPointerBridge"; - bridgePrefix = "bridged__"; - - fi = 0; -} - -function doWrapp(funcIndex_dw) { - - paramListC_dw = funcParamListC[funcIndex_dw]; - doWrapp_dw = 1; - - fullName_dw = funcFullName[funcIndex_dw]; - if (doWrapp_dw) { - metaInf_dw = funcMetaInf[funcIndex_dw]; - - if (match(metaInf_dw, /ARRAY:/)) { - #doWrapp_dw = 0; - } - if (match(metaInf_dw, /MAP:/)) { - #doWrapp_dw = 0; - } - if (match(fullName_dw, "^" bridgePrefix "File_")) { - doWrapp_dw = 0; - } - if (fullName_dw == "Engine_handleCommand") { - doWrapp_dw = 0; - } - # not wrapped, legacy C++ only - if (fullName_dw == "Engine_executeCommand") { - doWrapp_dw = 0; - } - } else { - print("Java-AIInterface: NOTE: native level: Callback: intentionally not wrapped: " fullName_dw); - } - - return doWrapp_dw; -} - -function createNativeFileName(fileName_fn, isHeader_fn) { - - absFileName_fn = NATIVE_GENERATED_SOURCE_DIR "/" fileName_fn; - if (isHeader_fn) { - absFileName_fn = absFileName_fn ".h"; - } else { - absFileName_fn = absFileName_fn ".c"; - } - - return absFileName_fn; -} - -function printNativeFP2F() { - - outFile_nh = createNativeFileName(nativeBridge, 1); - outFile_nc = createNativeFileName(nativeBridge, 0); - - printCommentsHeader(outFile_nh); - printCommentsHeader(outFile_nc); - - print("") >> outFile_nh; - print("#ifndef __CALLBACK_FUNCTION_POINTER_BRIDGE_H") >> outFile_nh; - print("#define __CALLBACK_FUNCTION_POINTER_BRIDGE_H") >> outFile_nh; - print("") >> outFile_nh; - print("#include \"ExternalAI/Interface/aidefines.h\"") >> outFile_nh; - print("") >> outFile_nh; - print("#include // size_t") >> outFile_nh; - print("#include // bool, true, false") >> outFile_nh; - print("") >> outFile_nh; - print("struct SSkirmishAICallback;") >> outFile_nh; - print("") >> outFile_nh; - print("#ifdef __cplusplus") >> outFile_nh; - print("extern \"C\" {") >> outFile_nh; - print("#endif") >> outFile_nh; - print("") >> outFile_nh; - print("void funcPntBrdg_addCallback(const size_t skirmishAIId, const struct SSkirmishAICallback* clb);") >> outFile_nh; - print("void funcPntBrdg_removeCallback(const size_t skirmishAIId);") >> outFile_nh; - print("") >> outFile_nh; - - print("") >> outFile_nc; - print("#include \"" nativeBridge ".h\"") >> outFile_nc; - print("") >> outFile_nc; - print("#include \"ExternalAI/Interface/SSkirmishAICallback.h\"") >> outFile_nc; - print("#include \"ExternalAI/Interface/AISCommands.h\"") >> outFile_nc; - print("") >> outFile_nc; - print("") >> outFile_nc; - print("#define id_clb_sizeMax 8192") >> outFile_nc; - print("static const struct SSkirmishAICallback* id_clb[id_clb_sizeMax];") >> outFile_nc; - print("") >> outFile_nc; - print("void funcPntBrdg_addCallback(const size_t skirmishAIId, const struct SSkirmishAICallback* clb) {") >> outFile_nc; - print(" //assert(skirmishAIId < id_clb_sizeMax);") >> outFile_nc; - print(" id_clb[skirmishAIId] = clb;") >> outFile_nc; - print("}") >> outFile_nc; - print("void funcPntBrdg_removeCallback(const size_t skirmishAIId) {") >> outFile_nc; - print(" //assert(skirmishAIId < id_clb_sizeMax);") >> outFile_nc; - print(" id_clb[skirmishAIId] = NULL;") >> outFile_nc; - print("}") >> outFile_nc; - print("") >> outFile_nc; - - # print the wrapping functions - for (i=0; i < fi; i++) { - fullName = funcFullName[i]; - retType = funcRetTypeC[i]; - paramList = funcParamListC[i]; - sub(/int skirmishAIId/, "int _skirmishAIId", paramList); - paramListNoTypes = removeParamTypes(paramList); - commentEol = funcCommentEol[i]; - - if (doWrapp(i)) { - # print function declaration to *.h - printFunctionComment_Common(outFile_nh, funcDocComment, i, ""); - - outName = bridgePrefix fullName; - - if (commentEol != "") { - commentEol = " // " commentEol; - } - print("EXPORT(" retType ") " outName "(" paramList ");" commentEol) >> outFile_nh; - print("") >> outFile_nh; - - # print function definition to *.c - print("EXPORT(" retType ") " outName "(" paramList ") {") >> outFile_nc; - - print(preConversion) >> outFile_nc; - if (hasRetParam) { - print("\t" retParamType " " retNameTmp " = id_clb[_skirmishAIId]->" fullName "(" paramListNoTypes ");") >> outFile_nc; - print(retParamConversion) >> outFile_nc; - } else { - condRet = "return "; - if (retType == "void") { - condRet = ""; - } - print("\t" condRet "id_clb[_skirmishAIId]->" fullName "(" paramListNoTypes ");") >> outFile_nc; - } - print("" "}") >> outFile_nc; - } else { - print("Note: The following function is intentionally not wrapped: " fullName); - } - } - - - print("") >> outFile_nh; - print("") >> outFile_nc; - - close(outFile_nh); - close(outFile_nc); -} - - -function wrappFunction(funcDef, commentEolTot) { - - doParse = 1; # add a function in case you want to exclude some - - if (doParse) { - size_funcParts = split(funcDef, funcParts, "(,)|(\\()|(\\);)"); - # because the empty part after ");" would count as part as well - size_funcParts--; - retType_c = trim(funcParts[1]); - fullName = funcParts[2]; - sub(/CALLING_CONV \*/, "", fullName); - sub(/\)/, "", fullName); - - # function parameters - paramList_c = ""; - paramList = ""; - - for (i=3; i<=size_funcParts && !match(funcParts[i], /.*\/\/.*/); i++) { - type_c = extractParamType(funcParts[i]); - type_c = cleanupCType(type_c); - name = extractParamName(funcParts[i]); - if (i == 3) { - cond_comma = ""; - } else { - cond_comma = ", "; - } - paramList_c = paramList_c cond_comma type_c " " name; - } - - funcFullName[fi] = fullName; - funcRetTypeC[fi] = retType_c; - funcParamListC[fi] = paramList_c; - funcParamList[fi] = paramList; - funcCommentEol[fi] = trim(commentEolTot); - storeDocLines(funcDocComment, fi); - fi++; - } else { - print("warninig: function intentionally NOT wrapped: " funcDef); - } -} - - - -# This function has to return true (1) if a doc comment (eg: /** foo bar */) -# can be deleted. -# If there is no special condition you want to apply, -# it should always return true (1), -# cause there are additional mechanism to prevent accidental deleting. -# see: commonDoc.awk -function canDeleteDocumentation() { - return isMultiLineFunc != 1; -} - - - -# save function pointer info into arrays -# ... 2nd, 3rd, ... line of a function pointer definition -{ - if (isMultiLineFunc) { # function is defined on one single line - funcIntermLine = $0; - # separate possible comment at end of line: //$ foo bar - commentEol = funcIntermLine; - if (sub(/.*\/\/\$/, "", commentEol)) { - commentEolTot = commentEolTot commentEol; - } - # remove possible comment at end of line: //$ foo bar - sub(/[ \t]*\/\/.*/, "", funcIntermLine); - funcIntermLine = trim(funcIntermLine); - funcSoFar = funcSoFar " " funcIntermLine; - if (match(funcSoFar, /;$/)) { - # function ends in this line - wrappFunction(funcSoFar, commentEolTot); - isMultiLineFunc = 0; - } - } -} -# 1st line of a function pointer definition -/^[^\/]*CALLING_CONV.*$/ { - - funcStartLine = $0; - # separate possible comment at end of line: //$ foo bar - commentEolTot = ""; - commentEol = funcStartLine; - if (sub(/.*\/\/\$/, "", commentEol)) { - commentEolTot = commentEolTot commentEol; - } - # remove possible comment at end of line: //$ foo bar - sub(/\/\/.*$/, "", funcStartLine); - funcStartLine = trim(funcStartLine); - if (match(funcStartLine, /;$/)) { - # function ends in this line - wrappFunction(funcStartLine, commentEolTot); - } else { - funcSoFar = funcStartLine; - isMultiLineFunc = 1; - } -} - - - -END { - # finalize things - - printNativeFP2F(); -} diff --git a/AI/Interfaces/Java/bin/native_wrappCommands.awk b/AI/Interfaces/Java/bin/native_wrappCommands.awk deleted file mode 100755 index 9de74c4a95f..00000000000 --- a/AI/Interfaces/Java/bin/native_wrappCommands.awk +++ /dev/null @@ -1,384 +0,0 @@ -#!/usr/bin/awk -f -# -# This awk script creates the C functions for wrapping the C command structs in: -# rts/ExternalAI/Interface/AISCommands.h -# -# Before running this script, you have to wrap the native callback struct -# into functions. -# -# This script uses functions from the following files: -# * common.awk -# * commonDoc.awk -# Variables that can be set on the command-line (with -v): -# * GENERATED_SOURCE_DIR the root generated sources dir -# default: "../src-generated/main" -# * NATIVE_GENERATED_SOURCE_DIR the native generated sources dir -# default: GENERATED_SOURCE_DIR + "/native" -# -# usage: -# awk -f thisScript.awk -f common.awk -f commonDoc.awk -# awk -f thisScript.awk -f common.awk -f commonDoc.awk \ -# -v 'NATIVE_GENERATED_SOURCE_DIR=/tmp/build/AI/Interfaces/Java/src-generated/main/native' -# - -BEGIN { - # initialize things - - # define the field splitter(-regex) - FS = "[ \t]+"; - - # These vars can be assigned externally, see file header. - # Set the default values if they were not supplied on the command line. - if (!GENERATED_SOURCE_DIR) { - GENERATED_SOURCE_DIR = "../src-generated/main"; - } - if (!NATIVE_GENERATED_SOURCE_DIR) { - NATIVE_GENERATED_SOURCE_DIR = GENERATED_SOURCE_DIR "/native"; - } - - nativeBridge = "CallbackFunctionPointerBridge"; - bridgePrefix = "bridged__"; - - indent = " "; - - ind_cmdTopics = 0; - ind_cmdStructs = 0; - insideCmdStruct = 0; -} - - -# Checks if a field is available and is no comment -function isFieldUsable(f) { - - valid = 0; - - if (f && !match(f, /.*\/\/.*/)) { - valid = 1; - } - - return valid; -} - - - - -function saveMember(ind_mem, member) { - - name = extractParamName(member); - type_c = extractCType(member); - - cmdsMembers_name[ind_cmdStructs, ind_mem] = name; - cmdsMembers_type_c[ind_cmdStructs, ind_mem] = type_c; -} - - -function doWrapp(ind_cmdStructs_dw) { - - doWrp_dw = 1; - - if (match(cmdsName[ind_cmdStructs_dw], /SharedMemArea/)) { - doWrp_dw = 0; - } else if (match(cmdsName[ind_cmdStructs_dw], /^SCallLuaRulesCommand$/)) { - doWrp_dw = 0; - } - - return doWrp_dw; -} - - -function createNativeFileName(fileName_fn, isHeader_fn) { - - absFileName_fn = NATIVE_GENERATED_SOURCE_DIR "/" fileName_fn; - if (isHeader_fn) { - absFileName_fn = absFileName_fn ".h"; - } else { - absFileName_fn = absFileName_fn ".c"; - } - - return absFileName_fn; -} - - -function printNativeFP2F() { - - outFile_nh = createNativeFileName(nativeBridge, 1); - outFile_nc = createNativeFileName(nativeBridge, 0); - - print("// END: COMMAND_WRAPPERS") >> outFile_nh; - print("") >> outFile_nh; - print("") >> outFile_nc; - - # print the command wrapping functions - for (cmdIndex=0; cmdIndex < ind_cmdStructs; cmdIndex++) { - topicName = cmdsTopicName[cmdIndex]; - topicValue = cmdsTopicNameValue[topicName]; - name = cmdsName[cmdIndex]; - metaInf = cmdsMetaInfo[cmdIndex]; - fullName = metaInf; - sub(/ .*$/, "", fullName); - sub(/^[^ \t]*/, "", metaInf); - - hasRetType = 0; - retType = "int"; - retParam = ""; - paramList = "int _skirmishAIId"; - firstMember = 0; - if (cmdsNumMembers[cmdIndex] > 0) { - for (m=firstMember; m < cmdsNumMembers[cmdIndex]; m++) { - memName = cmdsMembers_name[cmdIndex, m]; - memType_c = cmdsMembers_type_c[cmdIndex, m]; - - if (match(memName, /^ret_/) && !match(memType_c, /\*/)) { - retParam = memName; - retType = memType_c; - hasRetType = 1; - # rewrite the meta info - sub(memName, "RETURN", metaInf); - } else { - paramList = paramList ", " memType_c " " memName; - } - } - } - if (!hasRetType) { - metaInf = metaInf " error-return:0=OK"; - } - paramListNoTypes = removeParamTypes(paramList); - metaInf = trim(metaInf); - - - if (doWrapp(cmdIndex)) { - # print function declaration to *.h - - # Add the member comments to the main comment as @param attributes - numCmdDocLines = cmdsDocComment[cmdIndex, "*"]; - for (m=firstMember; m < cmdsNumMembers[cmdIndex]; m++) { - numLines = cmdMbrsDocComments[cmdIndex*1000 + m, "*"]; - if (numLines > 0) { - cmdsDocComment[cmdIndex, numCmdDocLines] = "@param " cmdsMembers_name[cmdIndex, m]; - } - for (l=0; l < numLines; l++) { - if (l == 0) { - _preDocLine = "@param " cmdsMembers_name[cmdIndex, m] " "; - } else { - _preDocLine = " " lengtAsSpaces(cmdsMembers_name[cmdIndex, m]) " "; - } - cmdsDocComment[cmdIndex, numCmdDocLines] = _preDocLine cmdMbrsDocComments[cmdIndex*1000 + m, l]; - numCmdDocLines++; - } - } - cmdsDocComment[cmdIndex, "*"] = numCmdDocLines; - - outName = bridgePrefix fullName; - - commentEol = ""; - if (metaInf != "") { - commentEol = " // " metaInf; - } - - if (match(fullName, /^Unit_/)) { - # To make this fit in smoothly with the OO structure, - # we want to present each unit command once for the unit class - # and once for the Group class. - - # An other thing we do, is move the common UnitAICommand params - # to the end of the params list, so we can supply default values - # for them more easily later on. - paramList_commonEnd = paramList; - sub(/, short options, int timeOut/, "", paramList_commonEnd); - paramList_commonEnd = paramList_commonEnd ", short options, int timeOut"; - - # Unit version: - paramList_unit = paramList_commonEnd; - sub(/int groupId, /, "", paramList_unit); - printFunctionComment_Common(outFile_nh, cmdsDocComment, cmdIndex, ""); - print("EXPORT(" retType ") " outName "(" paramList_unit ");" commentEol) >> outFile_nh; - print("") >> outFile_nh; - - # Group version: - paramList_group = paramList_commonEnd; - sub(/int unitId, /, "", paramList_group); - outName_group = outName; - sub(/Unit_/, "Group_", outName_group); - printFunctionComment_Common(outFile_nh, cmdsDocComment, cmdIndex, ""); - print("EXPORT(" retType ") " outName_group "(" paramList_group ");" commentEol) >> outFile_nh; - } else { - printFunctionComment_Common(outFile_nh, cmdsDocComment, cmdIndex, ""); - print("EXPORT(" retType ") " outName "(" paramList ");" commentEol) >> outFile_nh; - } - print("") >> outFile_nh; - - # print function definition to *.c - print("") >> outFile_nc; - if (match(fullName, /^Unit_/)) { - # inner version: - print("static " retType " _" outName "(" paramList ") {") >> outFile_nc; - } else { - print("EXPORT(" retType ") " outName "(" paramList ") {") >> outFile_nc; - } - print("") >> outFile_nc; - - print("\t" "struct S" name "Command commandData;") >> outFile_nc; - for (m=firstMember; m < cmdsNumMembers[cmdIndex]; m++) { - memName = cmdsMembers_name[cmdIndex, m]; - memType_c = cmdsMembers_type_c[cmdIndex, m]; - - if (memName == retParam) { - # do nothing - } else { - print("\t" "commandData." memName " = " memName ";") >> outFile_nc; - } - } - print("") >> outFile_nc; - - print("\t" "int _ret = id_clb[_skirmishAIId]->Engine_handleCommand(_skirmishAIId, COMMAND_TO_ID_ENGINE, -1, " topicName ", &commandData);") >> outFile_nc; - print("") >> outFile_nc; - - if (hasRetType) { - # this is unused, delete - print("\t" "if (_ret == 0) {") >> outFile_nc; - print("\t\t" "_ret = commandData." retParam ";") >> outFile_nc; - print("\t" "} else {") >> outFile_nc; - print("\t\t" "_ret = 0;") >> outFile_nc; - print("\t" "}") >> outFile_nc; - } - - print("\t" "return _ret;") >> outFile_nc; - print("}") >> outFile_nc; - - if (match(fullName, /^Unit_/)) { - paramListNoTypes = removeParamTypes(paramList); - - # Unit version: - print("") >> outFile_nc; - print("EXPORT(" retType ") " outName "(" paramList_unit ") {" commentEol) >> outFile_nc; - print("") >> outFile_nc; - print("\t" "const int groupId = -1;") >> outFile_nc; - print("\t" "return _" outName "(" paramListNoTypes ");") >> outFile_nc; - print("}") >> outFile_nc; - print("") >> outFile_nc; - - # Group version: - print("EXPORT(" retType ") " outName_group "(" paramList_group ") {" commentEol) >> outFile_nc; - print("") >> outFile_nc; - print("\t" "const int unitId = -1;") >> outFile_nc; - print("\t" "return _" outName "(" paramListNoTypes ");") >> outFile_nc; - print("}") >> outFile_nc; - } - } else { - print("Java-AIInterface: NOTE: native level: Commands: intentionally not wrapped: " fullName); - } - } - - print("// END: COMMAND_WRAPPERS") >> outFile_nh; - print("") >> outFile_nh; - print("#ifdef __cplusplus") >> outFile_nh; - print("} // extern \"C\"") >> outFile_nh; - print("#endif") >> outFile_nh; - print("") >> outFile_nh; - print("#endif // __CALLBACK_FUNCTION_POINTER_BRIDGE_H") >> outFile_nh; - print("") >> outFile_nh; - - print("") >> outFile_nc; - - close(outFile_nh); - close(outFile_nc); -} - - - - - - -# aggare te los command defines in order -/^[ \t]*COMMAND_.*$/ { - - sub(",", "", $4); - cmdsTopicNameValue[$2] = $4; -} - - - -# This function has to return true (1) if a doc comment (eg: /** foo bar */) -# can be deleted. -# If there is no special condition you want to apply, -# it should always return true (1), -# cause there are additional mechanism to prevent accidental deleting. -# see: commonDoc.awk -function canDeleteDocumentation() { - return isInsideCmdStruct != 1; -} - -################################################################################ -### BEGIN: parsing and saving the command structs - -# end of struct S*Command -/^}; \/\/\$ COMMAND_.*$/ { - - cmdsNumMembers[ind_cmdStructs] = ind_cmdMember; - cmdsTopicName[ind_cmdStructs] = $3; - metaInfo = $0; - sub(/^}; \/\/\$ COMMAND_[^ \t]+/, "", metaInfo); - metaInfo = trim(metaInfo); - cmdsMetaInfo[ind_cmdStructs] = metaInfo; - - if (doWrapp(ind_cmdStructs)) { - #printCommandJava(ind_cmdStructs); - } - - ind_cmdStructs++; - isInsideCmdStruct = 0; -} - - -# inside of struct S*Command -{ - if (isInsideCmdStruct == 1) { - size_tmpMembers = split($0, tmpMembers, ";"); - # cause there is an empty part behind the ';' - size_tmpMembers--; - for (i=1; i<=size_tmpMembers; i++) { - tmpMembers[i] = trim(tmpMembers[i]); - if (tmpMembers[i] == "" || match(tmpMembers[i], /^\/\//)) { - break; - } - # This would bork with more then 1000 members in a command, - # or more then 1000 commands - storeDocLines(cmdMbrsDocComments, ind_cmdStructs*1000 + ind_cmdMember); - saveMember(ind_cmdMember, tmpMembers[i]); - ind_cmdMember++; - } - } -} - -# beginning of struct S*Command -/^struct S.*Command( \{)?/ { - - isInsideCmdStruct = 1; - ind_cmdMember = 0; - commandName = $2; - sub(/^S/, "", commandName); - sub(/Command$/, "", commandName); - - isUnitCommand = match(commandName, /.*Unit$/); - - cmdsIsUnitCmd[ind_cmdStructs] = isUnitCommand; - cmdsName[ind_cmdStructs] = commandName; - storeDocLines(cmdsDocComment, ind_cmdStructs); -} - -# find COMMAND_TO_ID_ENGINE id -/COMMAND_TO_ID_ENGINE/ { - - cmdToIdEngine = $3; -} - -### END: parsing and saving the command structs -################################################################################ - - - -END { - # finalize things - - printNativeFP2F() -} diff --git a/AI/Interfaces/Java/data/InterfaceInfo.lua b/AI/Interfaces/Java/data/InterfaceInfo.lua deleted file mode 100644 index 13b586912a8..00000000000 --- a/AI/Interfaces/Java/data/InterfaceInfo.lua +++ /dev/null @@ -1,51 +0,0 @@ --- --- Info Definition Table format --- --- --- These keywords must be lowercase for LuaParser to read them. --- --- key: user defined or one of the AI_INTERFACE_PROPERTY_* defines in --- SAIInterfaceLibrary.h --- value: the value of the property --- desc: the description (could be used as a tooltip) --- --- --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -local infos = { - { - key = 'shortName', - value = 'Java', - desc = 'machine conform name.', - }, - { - key = 'version', - value = '0.1', - }, - { - key = 'name', - value = 'default Java AI Interface', - desc = 'human readable name.', - }, - { - key = 'description', - value = 'This interface is needed for Java AIs', - desc = 'tooltip.', - }, - { - key = 'url', - value = 'https://springrts.com/wiki/AIInterface:Java', - desc = 'URL with more detailed info about the AI', - }, - { - key = 'supportedLanguages', - value = 'Java (possily Groovy, JRuby, ...)', - }, - { - key = 'supportsLookup', - value = 'false', - }, -} - -return infos diff --git a/AI/Interfaces/Java/data/jvm.properties b/AI/Interfaces/Java/data/jvm.properties deleted file mode 100644 index a9642fc78ed..00000000000 --- a/AI/Interfaces/Java/data/jvm.properties +++ /dev/null @@ -1,86 +0,0 @@ -# These options are passed to the JVM instance in which all Java AIs run in. -# CAUTION: Please only change these settings if you know what you are doing!! -# For a list of possible options, please see: -# http://blogs.sun.com/watt/resource/jvm-options-list.html -# and: -# http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp -# If you use do not use the SUN JVM, -# the supported options may differ from this list. -# Each line that does not start with '#' or ';' and contains -# other characters then whitespaces is taken as an option. -# -# NOTE: When specifying relative paths to files (eg. log files), -# be aware that these files will likely end up in springs writable data dir, -# though this is not guaranteed, which means you may have to search for them -# in the CWD of the JVM, which differs between platforms. -# -# NOTE: When specifying paths to files (eg. log files), -# you can make use of ${home-dir}, which will be replaced with something like: -# "{abs-path-to-spring-writable-data-dir}/AI/Interfaces/Java/0.1" -# - -# NOTE: only really useful for debugging -# false: crash and report error when a JVM option was specified, -# that is unknown to the used JVM -# default: true -;jvm.arguments.ignoreUnrecognized=false - -# NOTE: change this if you need fancy stuff only -# specify as hex value, eg. 0x00010004 is JNI_VERSION_1_4 -# see jni.h (look for JNI_VERSION_* defines) -# default: 0x00010004 -;jvm.jni.version=0x00010002 - - -# NOTE: this should not be used, as it is auto-generated -;jvm.option.java.class.path=... -# you may simply store .jar and .class files in the jlib/ folder -# of the Java AI Interface or your Java Skirmish AI. -# the value of this property is appended to the generated java.class.path - -# NOTE: you shall NOT specify the following, as they are auto-generated: -;jvm.option.java.library.path=... -# simply store .dll, .so or .dylib files in the lib/ folder -# of the Java AI Interface or your Java Skirmish AI. -# the value of this property is appended to the generated java.library.path - -# Specify which type of the JVM to use -# possible values: client, server -# default: 32bit: client -# 64bit: server -;jvm.type=server - -# NOTE: do not use these, as the interface will ignore them; -# see jvm.option.java.*.path options above -;jvm.option.x=-Djava.class.path=... -;jvm.option.x=-Djava.library.path=... - -# footprint (memory) related -jvm.option.x=-Xms64M -jvm.option.x=-Xmx512M -jvm.option.x=-Xss512K -jvm.option.x=-Xoss400K - - -# Misc -;jvm.option.x=-XX:+AlwaysRestoreFPU -;jvm.option.x=-Djava.util.logging.config.file=./logging.properties - -# example settings for debugging -# logging related (only recommended when debugging) -;jvm.option.x=-Xcheck:jni -;jvm.option.x=-verbose:jni -;jvm.option.x=-XX:+UnlockDiagnosticVMOptions -;jvm.option.x=-XX:+LogVMOutput -;jvm.option.x=-XX:LogFile=${home-dir}/log/jvm-log.xml - -;jvm.option.x=-Xdebug -;jvm.option.x=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=7777 -# disable JIT (required for debugging under the classical VM) -;jvm.option.x=-Djava.compiler=NONE -# disables old JDB -;jvm.option.x=-Xnoagent - -;jvm.option.x=-XX:ErrorFile=./hs_err_pid.log -;jvm.option.x=-XX:+CheckJNICalls - diff --git a/AI/Interfaces/Java/pom.xml b/AI/Interfaces/Java/pom.xml deleted file mode 100644 index bddb566ede4..00000000000 --- a/AI/Interfaces/Java/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - 4.0.0 - - - - - - 0.1 - - - - com.springrts - common-spring - 1.0 - ../../../rts/build/maven/support/common/spring/pom.xml - - - com.springrts - ai-interface-java - ${my.version} - - jar - - Java AI Interface - Java Artificial Intelligence interface plugin for the Spring RTS engine - https://springrts.com/wiki/AIInterface:Java - 2008 - - - - - scm:git:git://github.com/spring/spring.git - scm:git:git@github.com:spring/spring.git - http://github.com/spring/spring/tree/master/AI/Interfaces/Java/ - - - diff --git a/AI/Interfaces/Java/src/main/java/com/springrts/ai/Util.java b/AI/Interfaces/Java/src/main/java/com/springrts/ai/Util.java deleted file mode 100644 index e3a45ebcc99..00000000000 --- a/AI/Interfaces/Java/src/main/java/com/springrts/ai/Util.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright (c) 2009 Robin Vobruba - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.springrts.ai; - - -import java.awt.Color; - -/** - * Contains utility functions used within all of the Java AI Interface code. - * - * @author hoijui.quaero@gmail.com - * @version 0.1 - */ -public final class Util { - - /** We need no instances of this class */ - private Util() {} - - public static short[] toShort3Array(final Color color) { - - short[] shortArr = new short[3]; - - shortArr[0] = (short) color.getRed(); - shortArr[1] = (short) color.getGreen(); - shortArr[2] = (short) color.getBlue(); - - return shortArr; - } - - public static Color toColor(final short[] shortArr) { - - Color color = new Color( - (int) shortArr[0], - (int) shortArr[1], - (int) shortArr[2] - ); - - return color; - } -} diff --git a/AI/Interfaces/Java/src/main/native/InterfaceDefines.h b/AI/Interfaces/Java/src/main/native/InterfaceDefines.h deleted file mode 100644 index ceb2ebcae59..00000000000 --- a/AI/Interfaces/Java/src/main/native/InterfaceDefines.h +++ /dev/null @@ -1,23 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -#ifndef _INTERFACE_DEFINES_H -#define _INTERFACE_DEFINES_H - -#ifdef __cplusplus -extern "C" { -#endif - -#define JAVA_SKIRMISH_AI_PROPERTY_CLASS_NAME "className" - -#define MY_LOG_FILE "interface-log.txt" -#define JAVA_AI_INTERFACE_LIBRARY_FILE_NAME "AIInterface.jar" -#define NATIVE_LIBS_DIR "lib" -#define JRE_LOCATION_FILE "jre-location.txt" - -#include // for NULL - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // _INTERFACE_DEFINES_H diff --git a/AI/Interfaces/Java/src/main/native/InterfaceExport.c b/AI/Interfaces/Java/src/main/native/InterfaceExport.c deleted file mode 100644 index d68714c722e..00000000000 --- a/AI/Interfaces/Java/src/main/native/InterfaceExport.c +++ /dev/null @@ -1,157 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -#include "InterfaceExport.h" - -#include "InterfaceDefines.h" -#include "JavaBridge.h" - -// generated at build time -#include "CallbackFunctionPointerBridge.h" - -#include "CUtils/Util.h" -#include "CUtils/SimpleLog.h" - -#include "ExternalAI/Interface/SAIInterfaceLibrary.h" -#include "ExternalAI/Interface/SAIInterfaceCallback.h" -#include "ExternalAI/Interface/SSkirmishAICallback.h" -#include "ExternalAI/Interface/SSkirmishAILibrary.h" - -#include // bool, true, false -#include // strlen(), strcat(), strcpy() -#include // malloc(), calloc(), free() - -static int interfaceId = -1; - -static const struct SAIInterfaceCallback* callback = NULL; -static struct SSkirmishAILibrary jvmSSkirmishAILibrary = {NULL, NULL, NULL, NULL}; - - -EXPORT(int) initStatic(int _interfaceId, const struct SAIInterfaceCallback* _callback) -{ - simpleLog_initcallback(_interfaceId, "Java Interface", _callback->Log_logsl, LOG_LEVEL_INFO); - - // initialize C part of the interface - interfaceId = _interfaceId; - callback = _callback; - - const char* const myShortName = callback->AIInterface_Info_getValueByKey(interfaceId, AI_INTERFACE_PROPERTY_SHORT_NAME); - const char* const myVersion = callback->AIInterface_Info_getValueByKey(interfaceId, AI_INTERFACE_PROPERTY_VERSION); - - if (myShortName == NULL || myVersion == NULL) { - simpleLog_logL(LOG_LEVEL_ERROR, "Couldn't fetch AI Name / Version \"%d\"", _interfaceId); - return -1; - } - - simpleLog_log("Initialized %s v%s AI Interface", myShortName, myVersion); - - // initialize Java part of the interface and the JVM - if (java_initStatic(interfaceId, callback)) { - simpleLog_logL(LOG_LEVEL_NOTICE, "Initialization successful."); - return 0; - } - - simpleLog_logL(LOG_LEVEL_ERROR, "Initialization failed."); - return -1; -} - -EXPORT(int) releaseStatic() -{ - // release Java part of the interface - if (java_releaseStatic() && java_unloadJNIEnv()) - return 0; - - return -1; -} - -EXPORT(enum LevelOfSupport) getLevelOfSupportFor(const char* engineVersion, int engineAIInterfaceGeneratedVersion) -{ - return LOS_Unknown; -} - - -// skirmish AI methods -enum LevelOfSupport CALLING_CONV proxy_skirmishAI_getLevelOfSupportFor( - const char* aiShortName, - const char* aiVersion, - const char* engineVersionString, - int engineVersionNumber, - const char* aiInterfaceShortName, - const char* aiInterfaceVersion -) { - return LOS_Unknown; -} - - -int CALLING_CONV proxy_skirmishAI_init(int skirmishAIId, const struct SSkirmishAICallback* aiCallback) -{ - int ret = -1; - - const char* const shortName = aiCallback->SkirmishAI_Info_getValueByKey(skirmishAIId, SKIRMISH_AI_PROPERTY_SHORT_NAME); - const char* const version = aiCallback->SkirmishAI_Info_getValueByKey(skirmishAIId, SKIRMISH_AI_PROPERTY_VERSION); - const char* const className = aiCallback->SkirmishAI_Info_getValueByKey(skirmishAIId, JAVA_SKIRMISH_AI_PROPERTY_CLASS_NAME); - - if (className != NULL) - ret = java_initSkirmishAIClass(shortName, version, className, skirmishAIId) ? 0 : 1; - - if (ret == 0) { - // init OK - funcPntBrdg_addCallback(skirmishAIId, aiCallback); - ret = java_skirmishAI_init(skirmishAIId, aiCallback); - } - - return ret; -} - -int CALLING_CONV proxy_skirmishAI_release(int skirmishAIId) -{ - // does nothing - const int ret = java_skirmishAI_release(skirmishAIId); - funcPntBrdg_removeCallback(skirmishAIId); - return ret; -} - -int CALLING_CONV proxy_skirmishAI_handleEvent(int skirmishAIId, int topicId, const void* data) -{ - return java_skirmishAI_handleEvent(skirmishAIId, topicId, data); -} - - -EXPORT(const struct SSkirmishAILibrary*) loadSkirmishAILibrary( - const char* const shortName, - const char* const version -) { - // all Java AI's run inside a single JVM proxied by this lib - if (jvmSSkirmishAILibrary.init == NULL) { - jvmSSkirmishAILibrary.getLevelOfSupportFor = &proxy_skirmishAI_getLevelOfSupportFor; -/* - jvmSSkirmishAILibrary.getInfo = proxy_skirmishAI_getInfo; - jvmSSkirmishAILibrary.getOptions = proxy_skirmishAI_getOptions; -*/ - jvmSSkirmishAILibrary.init = &proxy_skirmishAI_init; - jvmSSkirmishAILibrary.release = &proxy_skirmishAI_release; - jvmSSkirmishAILibrary.handleEvent = &proxy_skirmishAI_handleEvent; - } - - return &jvmSSkirmishAILibrary; -} - -EXPORT(int) unloadSkirmishAILibrary( - const char* const shortName, - const char* const version -) { - const char* const className = callback->SkirmishAI_Info_getValueByKey(interfaceId, shortName, version, JAVA_SKIRMISH_AI_PROPERTY_CLASS_NAME); - - if (java_releaseSkirmishAIClass(className)) - return 0; - - return -1; -} - -EXPORT(int) unloadAllSkirmishAILibraries() -{ - if (java_releaseAllSkirmishAIClasses()) - return 0; - - return -1; -} - diff --git a/AI/Interfaces/Java/src/main/native/InterfaceExport.h b/AI/Interfaces/Java/src/main/native/InterfaceExport.h deleted file mode 100644 index 20920ccb563..00000000000 --- a/AI/Interfaces/Java/src/main/native/InterfaceExport.h +++ /dev/null @@ -1,61 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -#ifndef _INTERFACE_EXPORT_H -#define _INTERFACE_EXPORT_H - -// check if the correct defines are set by the build system -#if !defined BUILDING_AI_INTERFACE -# error BUILDING_AI_INTERFACE should be defined when building AI Interfaces -#endif -#if !defined BUILDING_AI -# error BUILDING_AI should be defined when building AI Interfaces -#endif -#if defined BUILDING_SKIRMISH_AI -# error BUILDING_SKIRMISH_AI should not be defined when building AI Interfaces -#endif -#if defined SYNCIFY -# error SYNCIFY should not be defined when building AI Interfaces -#endif - - -#include "ExternalAI/Interface/aidefines.h" - -#ifdef __cplusplus -extern "C" { -#endif - -//#include "ExternalAI/Interface/ELevelOfSupport.h" - -struct SSkirmishAILibrary; -struct SAIInterfaceCallback; - -// for a list of the functions that have to be exported, -// see struct SAIInterfaceLibrary in: -// "rts/ExternalAI/Interface/SAIInterfaceLibrary.h" - - -// static AI interface library functions - -EXPORT(int) initStatic(int interfaceId, - const struct SAIInterfaceCallback* callback); -EXPORT(int) releaseStatic(); -//EXPORT(enum LevelOfSupport) getLevelOfSupportFor( -// const char* engineVersion, int engineAIInterfaceGeneratedVersion); - - -// skirmish AI related methods - -EXPORT(const struct SSkirmishAILibrary*) loadSkirmishAILibrary( - const char* const shortName, - const char* const version); -EXPORT(int) unloadSkirmishAILibrary( - const char* const shortName, - const char* const version); -EXPORT(int) unloadAllSkirmishAILibraries(); - - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // _INTERFACE_EXPORT_H diff --git a/AI/Interfaces/Java/src/main/native/JavaBridge.c b/AI/Interfaces/Java/src/main/native/JavaBridge.c deleted file mode 100644 index 1827f63c806..00000000000 --- a/AI/Interfaces/Java/src/main/native/JavaBridge.c +++ /dev/null @@ -1,1219 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -#include "JavaBridge.h" - -#include "InterfaceDefines.h" -#include "JvmLocater.h" -#include "JniUtil.h" -#include "EventsJNIBridge.h" -#include "CUtils/Util.h" -#include "CUtils/SimpleLog.h" -#include "CUtils/SharedLibrary.h" - -#include "ExternalAI/Interface/aidefines.h" -#include "ExternalAI/Interface/SAIInterfaceLibrary.h" -#include "ExternalAI/Interface/SAIInterfaceCallback.h" -#include "ExternalAI/Interface/SSkirmishAILibrary.h" -#include "ExternalAI/Interface/SSkirmishAICallback.h" -#include "System/SafeCStrings.h" -#include "lib/streflop/streflopC.h" - -#include - -#include // strlen(), strcat(), strcpy() -#include // malloc(), calloc(), free() -#include - -struct Properties { - size_t size; - const char** keys; - const char** values; -}; - -static int interfaceId = -1; - -static const struct SAIInterfaceCallback* callback = NULL; -static struct Properties jvmCfgProps = {0, NULL, NULL}; - -static size_t numSkirmishAIs = 0; -// static const size_t maxSkirmishAIs = 255; // MAX_AIS -#define maxSkirmishAIs 255 - -static size_t skirmishAIId_skirmishAiImpl[maxSkirmishAIs] = {999999}; - -static char* jAIClassNames[maxSkirmishAIs] = {NULL}; - -static jobject jAIInstances[maxSkirmishAIs] = {NULL}; -static jobject jAIClassLoaders[maxSkirmishAIs] = {NULL}; -static jobject jAICallBacks[maxSkirmishAIs] = {NULL}; - -// vars used to integrate the JVM -// it is loaded at runtime, not at loadtime -static sharedLib_t jvmSharedLib = NULL; - -typedef jint (JNICALL JNI_GetDefaultJavaVMInitArgs_t)(void* vmArgs); -typedef jint (JNICALL JNI_CreateJavaVM_t)(JavaVM** vm, void** jniEnv, void* vmArgs); -typedef jint (JNICALL JNI_GetCreatedJavaVMs_t)(JavaVM** vms, jsize vms_sizeMax, jsize* vms_size); - -static JNI_GetDefaultJavaVMInitArgs_t* JNI_GetDefaultJavaVMInitArgs_f; -static JNI_CreateJavaVM_t* JNI_CreateJavaVM_f; -static JNI_GetCreatedJavaVMs_t* JNI_GetCreatedJavaVMs_f; - - -// ### JNI global vars ### - -/// Java VM instance reference -static JavaVM* g_jvm = NULL; - -/// AI Callback class -static jclass g_cls_aiCallback = NULL; -/// AI Callback Constructor: AICallback(int skirmishAIId) -static jmethodID g_m_aiCallback_ctor_I = NULL; - -/// Basic AI interface. -static jclass g_cls_ai_int = NULL; - - - -// ### General helper functions following ### - -/// Sets the FPU state to how spring likes it -static inline void java_establishSpringEnv() { - // only detach in java_unloadJNIEnv - // (*g_jvm)->DetachCurrentThread(g_jvm); - streflop_init_Simple(); -} - -/// The JVM sets the environment it wants automatically, so this is a no-op -static inline void java_establishJavaEnv() {} - - -static inline size_t minSize(size_t size1, size_t size2) { - return (size1 < size2) ? size1 : size2; -} - -static const char* java_getValueByKey(const struct Properties* props, const char* key) { - return util_map_getValueByKey(props->size, props->keys, props->values, key); -} - - -// /** -// * Called when the JVM loads this native library. -// */ -// JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { -// return JNI_VERSION_1_6; -// } -// -// /** -// * Called when the JVM unloads this native library. -// */ -// JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {} - - - - -// ### JNI helper functions following ### - -/** - * Creates the AI Interface global Java class path. - * - * It will consist of the following: - * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/AIInterface.jar - * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/(j)?config/ - * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/(j)?config/[*].jar - * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/(j)?resources/ - * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/(j)?resources/[*].jar - * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/(j)?script/ - * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/(j)?script/[*].jar - * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/jlib/ - * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/jlib/[*].jar - * TODO: {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/common/jlib/ - * TODO: {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/common/jlib/[*].jar - */ -static bool java_createClassPath(char* classPathStr, const size_t classPathStr_sizeMax) -{ - // the dirs and .jar files in the following array - // will be concatenated with intermediate path separators - // to form the classPathStr - static const size_t classPath_sizeMax = 128; - char** classPath = (char**) calloc(classPath_sizeMax, sizeof(char*)); - size_t classPath_size = 0; - - // the Java AI Interfaces java library file path (.../AIInterface.jar) - // We need to search for this jar, instead of looking only where - // the AIInterface.so/InterfaceInfo.lua is, because on some systems - // (eg. Debian), the .so is in /usr/lib, and the .jar's are in /usr/shared. - char mainJarPath[2048]; - const bool located = callback->DataDirs_locatePath(interfaceId, - mainJarPath, sizeof(mainJarPath), - JAVA_AI_INTERFACE_LIBRARY_FILE_NAME, - false, false, false, false); - - if (!located) { - simpleLog_logL(LOG_LEVEL_ERROR, "Couldn't find %s", JAVA_AI_INTERFACE_LIBRARY_FILE_NAME); - return false; - } - - classPath[classPath_size++] = util_allocStrCpy(mainJarPath); - - if (!util_getParentDir(mainJarPath)) { - simpleLog_logL(LOG_LEVEL_ERROR, "Retrieving the parent dir of the path to AIInterface.jar (%s) failed.", mainJarPath); - return false; - } - - char* jarsDataDir = mainJarPath; - - // the directories in the following list will be searched for .jar files - // which will then be added to the classPathStr, plus the dirs will be added - // to the classPathStr directly, so you can keep .class files in there - static const size_t jarDirs_sizeMax = 128; - char** jarDirs = (char**) calloc(jarDirs_sizeMax, sizeof(char*)); - size_t jarDirs_size = 0; - - // add to classpath: - // {spring-data-dir}/Interfaces/Java/0.1/${x}/ - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "jconfig"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "config"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "jresources"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "resources"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "jscript"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "script"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "jlib"); - // "lib" is for native libs only - - // add the jar dirs (for .class files) and all contained .jars recursively - size_t jd, jf; - for (jd = 0; (jd < jarDirs_size) && (classPath_size < classPath_sizeMax); ++jd) { - if (util_fileExists(jarDirs[jd])) { - // add the dir directly - // For this to work properly with URLClassPathHandler, - // we have to ensure there is a '/' at the end, - // for the class-path-part to be recognized as a directory. - classPath[classPath_size++] = util_allocStrCat(2, jarDirs[jd], "/"); - - // add the contained jars recursively - static const size_t jarFiles_sizeMax = 128; - char** jarFiles = (char**) calloc(jarFiles_sizeMax, sizeof(char*)); - const size_t jarFiles_size = util_listFiles(jarDirs[jd], ".jar", jarFiles, true, jarFiles_sizeMax); - - for (jf = 0; (jf < jarFiles_size) && (classPath_size < classPath_sizeMax); ++jf) { - classPath[classPath_size++] = util_allocStrCatFSPath(2, jarDirs[jd], jarFiles[jf]); - FREE(jarFiles[jf]); - } - - FREE(jarFiles); - } - - FREE(jarDirs[jd]); - } - - FREE(jarDirs); - - - // concat the classpath entries - classPathStr[0] = '\0'; - if (classPath[0] != NULL) { - STRCAT_T(classPathStr, classPathStr_sizeMax, classPath[0]); - FREE(classPath[0]); - } - - size_t cp; - - for (cp = 1; cp < classPath_size; ++cp) { - if (classPath[cp] == NULL) - continue; - - STRCAT_T(classPathStr, classPathStr_sizeMax, ENTRY_DELIM); - STRCAT_T(classPathStr, classPathStr_sizeMax, classPath[cp]); - FREE(classPath[cp]); - } - - FREE(classPath); - return true; -} - -/** - * Creates a Skirmish AI local Java class path. - * - * It will consist of the following: - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/SkirmishAI.jar - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/(j)?config/ - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/(j)?config/[*].jar - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/(j)?resources/ - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/(j)?resources/[*].jar - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/(j)?script/ - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/(j)?script/[*].jar - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/jlib/ - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/jlib/[*].jar - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/(j)?config/ - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/(j)?config/[*].jar - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/(j)?resources/ - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/(j)?resources/[*].jar - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/(j)?script/ - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/(j)?script/[*].jar - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/jlib/ - * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/jlib/[*].jar - */ -static size_t java_createAIClassPath( - const char* shortName, - const char* version, - char** classPathParts, - const size_t classPathParts_sizeMax -) { - size_t classPathParts_size = 0; - - // the .jar files in the following list will be added to the classpath - const size_t jarFiles_sizeMax = classPathParts_sizeMax; - char** jarFiles = (char**) calloc(jarFiles_sizeMax, sizeof(char*)); - size_t jarFiles_size = 0; - - const char* const skirmDD = - callback->SkirmishAI_Info_getValueByKey(interfaceId, - shortName, version, - SKIRMISH_AI_PROPERTY_DATA_DIR); - if (skirmDD == NULL) { - simpleLog_logL(LOG_LEVEL_ERROR, - "Retrieving the data-dir of Skirmish AI %s-%s failed.", - shortName, version); - } - // {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/SkirmishAI.jar - jarFiles[jarFiles_size++] = util_allocStrCatFSPath(2, - skirmDD, "SkirmishAI.jar"); - - // the directories in the following list will be searched for .jar files - // which then will be added to the classpath, plus they will be added - // to the classpath directly, so you can keep .class files in there - const size_t jarDirs_sizeMax = classPathParts_sizeMax; - char** jarDirs = (char**) calloc(jarDirs_sizeMax, sizeof(char*)); - size_t jarDirs_size = 0; - - // add to classpath ... - - // {spring-data-dir}/Skirmish/MyJavaAI/0.1/SkirmishAI/ - // this can be useful for AI devs while testing, - // if they do not want to put everything into a jar all the time - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "SkirmishAI"); - - // add to classpath: - // {spring-data-dir}/Skirmish/MyJavaAI/0.1/${x}/ - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "jconfig"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "config"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "jresources"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "resources"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "jscript"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "script"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "jlib"); - // "lib" is for native libs only - - // add the dir common for all versions of the Skirmish AI, - // if it is specified and exists - const char* const skirmDDCommon = - callback->SkirmishAI_Info_getValueByKey(interfaceId, - shortName, version, - SKIRMISH_AI_PROPERTY_DATA_DIR_COMMON); - - if (skirmDDCommon != NULL) { - // add to classpath: - // {spring-data-dir}/Skirmish/MyJavaAI/common/${x}/ - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "jconfig"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "config"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "jresources"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "resources"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "jscript"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "script"); - jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "jlib"); - // "lib" is for native libs only - } - - // add the directly specified .jar files - size_t jf; - for (jf = 0; (jf < jarFiles_size) && (classPathParts_size < classPathParts_sizeMax); ++jf) { - classPathParts[classPathParts_size++] = util_allocStrCpy(jarFiles[jf]); - FREE(jarFiles[jf]); - } - - // add the dirs and the contained .jar files - size_t jd, sjf; - for (jd = 0; (jd < jarDirs_size) && (classPathParts_size < classPathParts_sizeMax); ++jd) { - if (jarDirs[jd] != NULL && util_fileExists(jarDirs[jd])) { - // add the jar dir (for .class files) - // For this to work properly with URLClassPathHandler, - // we have to ensure there is a '/' at the end, - // for the class-path-part to be recognized as a directory. - classPathParts[classPathParts_size++] = util_allocStrCat(2, jarDirs[jd], "/"); - - // add the jars in the dir - const size_t subJarFiles_sizeMax = classPathParts_sizeMax - classPathParts_size; - char** subJarFiles = (char**) calloc(subJarFiles_sizeMax, sizeof(char*)); - const size_t subJarFiles_size = util_listFiles(jarDirs[jd], ".jar", subJarFiles, true, subJarFiles_sizeMax); - - for (sjf = 0; (sjf < subJarFiles_size) && (classPathParts_size < classPathParts_sizeMax); ++sjf) { - // .../[*].jar - classPathParts[classPathParts_size++] = util_allocStrCatFSPath(2, jarDirs[jd], subJarFiles[sjf]); - FREE(subJarFiles[sjf]); - } - - FREE(subJarFiles); - } - - FREE(jarDirs[jd]); - } - - FREE(jarDirs); - FREE(jarFiles); - - return classPathParts_size; -} - -static jobject java_createAIClassLoader(JNIEnv* env, const char* shortName, const char* version) -{ - static const size_t classPathParts_sizeMax = 512; - char** classPathParts = (char**) calloc(classPathParts_sizeMax, sizeof(char*)); - const size_t classPathParts_size = java_createAIClassPath(shortName, version, classPathParts, classPathParts_sizeMax); - - jobject o_jClsLoader = NULL; - jobjectArray o_cppURLs = jniUtil_createURLArray(env, classPathParts_size); - - if (o_cppURLs != NULL) { -#ifdef _WIN32 - static const char* FILE_URL_PREFIX = "file:///"; -#else // _WIN32 - static const char* FILE_URL_PREFIX = "file://"; -#endif // _WIN32 - size_t cpp; - for (cpp = 0; cpp < classPathParts_size; ++cpp) { - #ifdef _WIN32 - // we can not use windows path separators in file URLs - util_strReplaceChar(classPathParts[cpp], '\\', '/'); - #endif - - char* str_fileUrl = util_allocStrCat(2, FILE_URL_PREFIX, classPathParts[cpp]); - // TODO: check/test if this is allowed/ok - FREE(classPathParts[cpp]); - simpleLog_logL(LOG_LEVEL_INFO, - "Skirmish AI %s %s class-path part %i: \"%s\"", - shortName, version, cpp, str_fileUrl); - jobject jurl_fileUrl = jniUtil_createURLObject(env, str_fileUrl); - FREE(str_fileUrl); - if (jurl_fileUrl == NULL) { - simpleLog_logL(LOG_LEVEL_ERROR, - "Skirmish AI %s %s class-path part %i (\"%s\"): failed to create a URL", - shortName, version, cpp, str_fileUrl); - o_cppURLs = NULL; - break; - } - const bool inserted = jniUtil_insertURLIntoArray(env, o_cppURLs, cpp, jurl_fileUrl); - if (!inserted) { - simpleLog_logL(LOG_LEVEL_ERROR, - "Skirmish AI %s %s class-path part %i (\"%s\"): failed to insert", - shortName, version, cpp, str_fileUrl); - o_cppURLs = NULL; - break; - } - } - } - - if (o_cppURLs != NULL) { - if ((o_jClsLoader = jniUtil_createURLClassLoader(env, o_cppURLs)) != NULL) - o_jClsLoader = jniUtil_makeGlobalRef(env, o_jClsLoader, "Skirmish AI class-loader"); - } - - FREE(classPathParts); - return o_jClsLoader; -} - -/** - * Load the interfaces JVM properties file. - */ -static bool java_readJvmCfgFile(struct Properties* props) -{ - bool read = false; - - const size_t props_sizeMax = 256; - - props->size = 0; - props->keys = (const char**) calloc(props_sizeMax, sizeof(char*)); - props->values = (const char**) calloc(props_sizeMax, sizeof(char*)); - - // ### read JVM options config file ### - char jvmPropFile[2048]; - bool located = callback->DataDirs_locatePath(interfaceId, jvmPropFile, sizeof(jvmPropFile), JVM_PROPERTIES_FILE, false, false, false, false); - - // if the version specific file does not exist, - // try to get the common one - if (!located) - located = callback->DataDirs_locatePath(interfaceId, jvmPropFile, sizeof(jvmPropFile), JVM_PROPERTIES_FILE, false, false, false, true); - - if (located) { - props->size = util_parsePropertiesFile(jvmPropFile, props->keys, props->values, props_sizeMax); - read = true; - simpleLog_logL(LOG_LEVEL_INFO, "JVM: arguments loaded from: %s", jvmPropFile); - } else { - props->size = 0; - read = false; - simpleLog_logL(LOG_LEVEL_INFO, "JVM: arguments NOT loaded"); - } - - return read; -} - -/** - * Creates the Java library path. - * -> where native shared libraries are searched - * - * It will consist of the following: - * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/ - * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/lib/ - * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/common/ - * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/common/lib/ - */ -static bool java_createNativeLibsPath(char* libraryPath, const size_t libraryPath_sizeMax) -{ - // {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/ - const char* const dd_r = callback->AIInterface_Info_getValueByKey(interfaceId, AI_INTERFACE_PROPERTY_DATA_DIR); - - if (dd_r == NULL) { - simpleLog_logL(LOG_LEVEL_ERROR, "Unable to find read-only data-dir."); - return false; - } - - STRCPY_T(libraryPath, libraryPath_sizeMax, dd_r); - - // {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/lib/ - char dd_lib_r[2048]; - const bool located = callback->DataDirs_locatePath(interfaceId, dd_lib_r, sizeof(dd_lib_r), NATIVE_LIBS_DIR, false, false, true, false); - - if (!located) { - simpleLog_logL(LOG_LEVEL_NOTICE, "Unable to find read-only native libs data-dir (optional): %s", NATIVE_LIBS_DIR); - } else { - STRCAT_T(libraryPath, libraryPath_sizeMax, ENTRY_DELIM); - STRCAT_T(libraryPath, libraryPath_sizeMax, dd_lib_r); - } - - - // {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/common/ - const char* const dd_r_common = callback->AIInterface_Info_getValueByKey(interfaceId, AI_INTERFACE_PROPERTY_DATA_DIR_COMMON); - - if (dd_r_common == NULL || !util_fileExists(dd_r_common)) { - simpleLog_logL(LOG_LEVEL_NOTICE, "Unable to find common read-only data-dir (optional)."); - } else { - STRCAT_T(libraryPath, libraryPath_sizeMax, ENTRY_DELIM); - STRCAT_T(libraryPath, libraryPath_sizeMax, dd_r_common); - } - - - // {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/common/lib/ - if (dd_r_common != NULL) { - char dd_lib_r_common[2048]; - const bool located = callback->DataDirs_locatePath(interfaceId, dd_lib_r_common, sizeof(dd_lib_r_common), NATIVE_LIBS_DIR, false, false, true, true); - - if (!located || !util_fileExists(dd_lib_r_common)) { - simpleLog_logL(LOG_LEVEL_NOTICE, "Unable to find common read-only native libs data-dir (optional)."); - } else { - STRCAT_T(libraryPath, libraryPath_sizeMax, ENTRY_DELIM); - STRCAT_T(libraryPath, libraryPath_sizeMax, dd_lib_r_common); - } - } - - return true; -} - - -static bool java_createJavaVMInitArgs(struct JavaVMInitArgs* vm_args, const struct Properties* jvmProps) -{ - // jint jniVersion = JNI_VERSION_1_1; - // jint jniVersion = JNI_VERSION_1_2; - jint jniVersion = JNI_VERSION_1_4; - // jint jniVersion = JNI_VERSION_1_6; - - char classPath [8 * 1024] = {0}; - char classPathOpt[8 * 1024] = {0}; - char libraryPath [4 * 1024] = {0}; - char libraryPathOpt[4 * 1024] = {0}; - - bool ignoreUnrecognized = true; - - if (jvmProps != NULL) { - const char* jniVersionFromCfg = java_getValueByKey(jvmProps, "jvm.jni.version"); - const char* ignoreUnrecognizedFromCfg = java_getValueByKey(jvmProps, "jvm.arguments.ignoreUnrecognized"); - - // ### evaluate JNI version to use ### - if (jniVersionFromCfg != NULL) { - const unsigned long int jniVersion_tmp = strtoul(jniVersionFromCfg, NULL, 16); - - if (jniVersion_tmp != 0 /*&& jniVersion_tmp != ULONG_MAX*/) - jniVersion = (jint) jniVersion_tmp; - } - - // ### check if unrecognized JVM options should be ignored ### - // if false, the JVM creation will fail if an - // unknown or invalid option was specified - if (ignoreUnrecognizedFromCfg != NULL && !util_strToBool(ignoreUnrecognizedFromCfg)) - ignoreUnrecognized = false; - } - - simpleLog_logL(LOG_LEVEL_INFO, "JVM: JNI version: %#x", jniVersion); - vm_args->version = jniVersion; - - - if (ignoreUnrecognized) { - simpleLog_logL(LOG_LEVEL_INFO, "JVM: ignoring unrecognized options"); - vm_args->ignoreUnrecognized = JNI_TRUE; - } else { - simpleLog_logL(LOG_LEVEL_INFO, "JVM: NOT ignoring unrecognized options"); - vm_args->ignoreUnrecognized = JNI_FALSE; - } - - - // ### create the Java class-path option ### - // autogenerate the class path - if (!java_createClassPath(classPath, sizeof(classPath))) { - simpleLog_logL(LOG_LEVEL_ERROR, "Failed creating Java class-path."); - return false; - } - if (jvmProps != NULL) { - // ..., and append the part from the jvm options properties file, - // if it is specified there - const char* clsPathFromCfg = java_getValueByKey(jvmProps, "jvm.option.java.class.path"); - - if (clsPathFromCfg != NULL) { - STRCAT_T(classPath, sizeof(classPath), ENTRY_DELIM); - STRCAT_T(classPath, sizeof(classPath), clsPathFromCfg); - } - } - - - // create the java.class.path option - STRCPY_T(classPathOpt, sizeof(classPathOpt), "-Djava.class.path="); - STRCAT_T(classPathOpt, sizeof(classPathOpt), classPath); - - - // ### create the Java library-path option ### - // autogenerate the java library path - if (!java_createNativeLibsPath(libraryPath, sizeof(libraryPath))) { - simpleLog_logL(LOG_LEVEL_ERROR, "Failed creating Java library-path."); - return false; - } - if (jvmProps != NULL) { - // ..., and append the part from the jvm options properties file, - // if it is specified there - const char* libPathFromCfg = java_getValueByKey(jvmProps, "jvm.option.java.library.path"); - - if (libPathFromCfg != NULL) { - STRCAT_T(libraryPath, sizeof(libraryPath), ENTRY_DELIM); - STRCAT_T(libraryPath, sizeof(libraryPath), libPathFromCfg); - } - } - - // create the java.library.path option ... - // autogenerate it, and append the part from the jvm options file, - // if it is specified there - STRCPY_T(libraryPathOpt, sizeof(libraryPathOpt), "-Djava.library.path="); - STRCAT_T(libraryPathOpt, sizeof(libraryPathOpt), libraryPath); - - - // ### create and set all JVM options ### - const char* strOptions[64]; - size_t numOpts = 0; - - strOptions[numOpts++] = classPathOpt; - strOptions[numOpts++] = libraryPathOpt; - - static const char* const JCPVAL = "-Djava.class.path="; - static const char* const JLPVAL = "-Djava.library.path="; - const size_t JCPVAL_size = strlen(JCPVAL); - const size_t JLPVAL_size = strlen(JCPVAL); - - if (jvmProps != NULL) { - // ### add string options from the JVM config file with property name "jvm.option.x" ### - int i; - for (i = 0; i < jvmProps->size; ++i) { - if (strcmp(jvmProps->keys[i], "jvm.option.x") != 0) - continue; - - const char* const val = jvmProps->values[i]; - const size_t val_size = strlen(val); - // ignore "-Djava.class.path=..." - // and "-Djava.library.path=..." options - if (strncmp(val, JCPVAL, minSize(val_size, JCPVAL_size)) != 0 && - strncmp(val, JLPVAL, minSize(val_size, JLPVAL_size)) != 0) { - strOptions[numOpts++] = val; - } - } - } else { - // ### ... or set default ones, if the JVM config file was not found ### - simpleLog_logL(LOG_LEVEL_WARNING, "JVM: properties file ("JVM_PROPERTIES_FILE") not found; using default options."); - - strOptions[numOpts++] = "-Xms64M"; - strOptions[numOpts++] = "-Xmx512M"; - strOptions[numOpts++] = "-Xss512K"; - strOptions[numOpts++] = "-Xoss400K"; - - #if defined DEBUG - strOptions[numOpts++] = "-Xcheck:jni"; - strOptions[numOpts++] = "-verbose:jni"; - strOptions[numOpts++] = "-XX:+UnlockDiagnosticVMOptions"; - strOptions[numOpts++] = "-XX:+LogVMOutput"; - - strOptions[numOpts++] = "-Xdebug"; - strOptions[numOpts++] = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=7777"; - // disable JIT (required for debugging under the classical VM) - strOptions[numOpts++] = "-Djava.compiler=NONE"; - // disable old JDB - strOptions[numOpts++] = "-Xnoagent"; - #endif // defined DEBUG - } - - vm_args->options = (struct JavaVMOption*) calloc(numOpts, sizeof(struct JavaVMOption)); - vm_args->nOptions = 0; - - // fill strOptions into the JVM options - simpleLog_logL(LOG_LEVEL_INFO, "JVM: options:", numOpts); - char dd_rw[2048] = {'\0'}; - callback->DataDirs_locatePath(interfaceId, dd_rw, sizeof(dd_rw), "", true, true, true, false); - size_t i; - - for (i = 0; i < numOpts; ++i) { - char* tmpOptionString = util_allocStrReplaceStr(strOptions[i], "${home-dir}", dd_rw); - - // do not add empty options - if (tmpOptionString == NULL) - continue; - - if (strlen(tmpOptionString) == 0) { - free(tmpOptionString); - tmpOptionString = NULL; - continue; - } - - vm_args->options[vm_args->nOptions ].optionString = tmpOptionString; - vm_args->options[vm_args->nOptions++].extraInfo = NULL; - - simpleLog_logL(LOG_LEVEL_INFO, "JVM option %ul: %s", vm_args->nOptions - 1, tmpOptionString); - } - - simpleLog_logL(LOG_LEVEL_INFO, ""); - return true; -} - - - -static JNIEnv* java_reattachCurrentThread(JavaVM* jvm) -{ - simpleLog_logL(LOG_LEVEL_DEBUG, "Reattaching current thread..."); - - JNIEnv* env = NULL; - - // const jint res = (*jvm)->AttachCurrentThreadAsDaemon(jvm, (void**) &env, NULL); - const jint res = (*jvm)->AttachCurrentThread(jvm, (void**) &env, NULL); - - if (res != 0) { - env = NULL; - simpleLog_logL(LOG_LEVEL_ERROR, "Failed attaching JVM to current thread(2): %i - %s", res, jniUtil_getJniRetValDescription(res)); - } - - return env; -} - -static JNIEnv* java_getJNIEnv(bool preload) -{ - if (g_jvm != NULL) - return java_reattachCurrentThread(g_jvm); - - assert(preload); - simpleLog_logL(LOG_LEVEL_INFO, "Creating the JVM."); - - JNIEnv* ret = NULL; - JNIEnv* env = NULL; - JavaVM* jvm = NULL; - - struct JavaVMInitArgs vm_args; - memset(&vm_args, 0, sizeof(vm_args)); - - jint res = 0; - - if (!java_createJavaVMInitArgs(&vm_args, &jvmCfgProps)) { - simpleLog_logL(LOG_LEVEL_ERROR, "Failed initializing JVM init-arguments."); - goto end; - } - - // Looking for existing JVMs might be problematic: they could - // have been initialized with other JVM-arguments then we need. - // But as we can not use DestroyJavaVM (it makes creating a new - // one for the same process impossible, a (SUN?) JVM limitation) - // we have to do this anyway to support /aireload and /aicontrol - // for Java Skirmish AIs. - // - simpleLog_logL(LOG_LEVEL_INFO, "looking for existing JVMs ..."); - jsize numJVMsFound = 0; - - // grab the first Java VM that has been created - if ((res = JNI_GetCreatedJavaVMs_f(&jvm, 1, &numJVMsFound)) != 0) { - jvm = NULL; - simpleLog_logL(LOG_LEVEL_ERROR, "Failed fetching list of running JVMs: %i - %s", res, jniUtil_getJniRetValDescription(res)); - goto end; - } - - simpleLog_logL(LOG_LEVEL_INFO, "number of existing JVMs: %i", numJVMsFound); - - if (numJVMsFound > 0) { - simpleLog_logL(LOG_LEVEL_INFO, "using an already running JVM."); - } else { - simpleLog_logL(LOG_LEVEL_INFO, "creating JVM..."); - - if ((res = JNI_CreateJavaVM_f(&jvm, (void**) &env, &vm_args)) != 0 || (*env)->ExceptionCheck(env)) { - simpleLog_logL(LOG_LEVEL_ERROR, "Failed to create Java VM: %i - %s", res, jniUtil_getJniRetValDescription(res)); - goto end; - } - } - - // free the JavaVMInitArgs content - jint i; - for (i = 0; i < vm_args.nOptions; ++i) { - FREE(vm_args.options[i].optionString); - } - FREE(vm_args.options); - - //res = (*jvm)->AttachCurrentThreadAsDaemon(jvm, (void**) &env, NULL); - res = (*jvm)->AttachCurrentThread(jvm, (void**) &env, NULL); - - if (res < 0 || (*env)->ExceptionCheck(env)) { - if ((*env)->ExceptionCheck(env)) - (*env)->ExceptionDescribe(env); - - simpleLog_logL(LOG_LEVEL_ERROR, "Failed to attach JVM to current thread: %i - %s", res, jniUtil_getJniRetValDescription(res)); - goto end; - } - -end: - if (env == NULL || jvm == NULL || (*env)->ExceptionCheck(env) || res != 0) { - simpleLog_logL(LOG_LEVEL_ERROR, "JVM: Failed creating."); - - if (env != NULL && (*env)->ExceptionCheck(env)) - (*env)->ExceptionDescribe(env); - - if (jvm != NULL) { - // never destroy the JVM, doing so prevents it from being created again for the same thread - // res = (*jvm)->DestroyJavaVM(jvm); - } - - g_jvm = NULL; - ret = NULL; - } else { - g_jvm = jvm; - ret = env; - } - - return ret; -} - - -bool java_unloadJNIEnv() -{ - if (g_jvm == NULL) - return true; - - simpleLog_logL(LOG_LEVEL_INFO, "JVM: Unloading ..."); - - #if 0 - // JNIEnv* jniEnv = java_getJNIEnv(false); - - // We have to be the ONLY running thread (native and Java) - // this may not help, but will not hurt either - // - // // jint res = (*g_jvm)->AttachCurrentThreadAsDaemon(g_jvm, (void**) &g_jniEnv, NULL); - // jint res = jvm->AttachCurrentThread((void**) &jniEnv, NULL); - #endif - - #if 0 - if (res < 0 || (*g_jniEnv)->ExceptionCheck(g_jniEnv)) { - if ((*g_jniEnv)->ExceptionCheck(g_jniEnv)) - (*g_jniEnv)->ExceptionDescribe(g_jniEnv); - - simpleLog_logL(LOG_LEVEL_ERROR, "JVM: Can not Attach to the current thread: %i - %s", res, jniUtil_getJniRetValDescription(res)); - return false; - } - #endif - - - const jint res = (*g_jvm)->DetachCurrentThread(g_jvm); - - if (res != 0) { - simpleLog_logL(LOG_LEVEL_ERROR, "JVM: Failed detaching current thread: %i - %s", res, jniUtil_getJniRetValDescription(res)); - return false; - } - - // never destroy the JVM because then it can not be created again - // in the same thread; will always fail with return value -1 (see - // java_getJNIEnv) - #if 0 - if ((res = (*g_jvm)->DestroyJavaVM(g_jvm)) != 0) { - simpleLog_logL(LOG_LEVEL_ERROR, "JVM: Failed destroying: %i - %s", res, jniUtil_getJniRetValDescription(res)); - return false; - } - - simpleLog_logL(LOG_LEVEL_NOTICE, "JVM: Successfully destroyed"); - g_jvm = NULL; - #endif - - java_establishSpringEnv(); - return true; -} - - -bool java_initStatic(int _interfaceId, const struct SAIInterfaceCallback* _callback) -{ - interfaceId = _interfaceId; - callback = _callback; - - // Read the jvm properties config file - java_readJvmCfgFile(&jvmCfgProps); - - size_t t; - for (t = 0; t < maxSkirmishAIs; ++t) { - skirmishAIId_skirmishAiImpl[t] = 999999; - } - - size_t sai; - for (sai = 0; sai < maxSkirmishAIs; ++sai) { - jAIClassNames[sai] = NULL; - jAIInstances[sai] = NULL; - jAIClassLoaders[sai] = NULL; - } - - // dynamically load the JVM - char jreLocationFile[2048]; - const bool located = callback->DataDirs_locatePath(interfaceId, jreLocationFile, sizeof(jreLocationFile), JRE_LOCATION_FILE, false, false, false, false); - char jrePath[1024]; - char jvmLibPath[1024]; - - if (!GetJREPath(jrePath, sizeof(jrePath), located ? jreLocationFile : NULL, NULL)) { - simpleLog_logL(LOG_LEVEL_ERROR, "Failed locating a JRE installation, you may specify the JAVA_HOME env var."); - return false; - } - - simpleLog_logL(LOG_LEVEL_NOTICE, "Using JRE (can be changed with JAVA_HOME): %s", jrePath); - -#if defined __arch64__ - static const char* defJvmType = "server"; -#else - static const char* defJvmType = "client"; -#endif - const char* jvmType = java_getValueByKey(&jvmCfgProps, "jvm.type"); - - if (jvmType == NULL) - jvmType = defJvmType; - - if (!GetJVMPath(jrePath, jvmType, jvmLibPath, sizeof(jvmLibPath), NULL)) { - simpleLog_logL(LOG_LEVEL_ERROR, "Failed locating the %s version of the JVM, please contact spring devs.", jvmType); - return false; - } - - if (!sharedLib_isLoaded(jvmSharedLib = sharedLib_load(jvmLibPath))) { - simpleLog_logL(LOG_LEVEL_ERROR, "Failed to load the JVM at \"%s\".", jvmLibPath); - return false; - } - - simpleLog_logL(LOG_LEVEL_NOTICE, "Successfully loaded the JVM shared library at \"%s\".", jvmLibPath); - - if ((JNI_GetDefaultJavaVMInitArgs_f = (JNI_GetDefaultJavaVMInitArgs_t*) sharedLib_findAddress(jvmSharedLib, "JNI_GetDefaultJavaVMInitArgs")) == NULL) { - simpleLog_logL(LOG_LEVEL_ERROR, "Failed to load the JVM, function \"%s\" not exported.", "JNI_GetDefaultJavaVMInitArgs"); - return false; - } - - if ((JNI_CreateJavaVM_f = (JNI_CreateJavaVM_t*) sharedLib_findAddress(jvmSharedLib, "JNI_CreateJavaVM")) == NULL) { - simpleLog_logL(LOG_LEVEL_ERROR, "Failed to load the JVM, function \"%s\" not exported.", "JNI_CreateJavaVM"); - return false; - } - - if ((JNI_GetCreatedJavaVMs_f = (JNI_GetCreatedJavaVMs_t*) sharedLib_findAddress(jvmSharedLib, "JNI_GetCreatedJavaVMs")) == NULL) { - simpleLog_logL(LOG_LEVEL_ERROR, "Failed to load the JVM, function \"%s\" not exported.", "JNI_GetCreatedJavaVMs"); - return false; - } - - java_establishJavaEnv(); - JNIEnv* env = java_getJNIEnv(true); - const bool loaded = (env != NULL); - const bool inited = (loaded && eventsJniBridge_initStatic(env, maxSkirmishAIs) == 0); - java_establishSpringEnv(); - - return inited; -} - -bool java_releaseStatic() -{ - sharedLib_unload(jvmSharedLib); - jvmSharedLib = NULL; - - FREE(jvmCfgProps.keys); - FREE(jvmCfgProps.values); - return true; -} - - - -static jobject java_createAICallback(JNIEnv* env, const struct SSkirmishAICallback* aiCallback, int skirmishAIId) { - (void) aiCallback; - - // initialize the AI Callback class, if not yet done - if (g_cls_aiCallback == NULL) { - // get the AI Callback class - if ((g_cls_aiCallback = jniUtil_findClass(env, CLS_AI_CALLBACK)) == NULL) - return NULL; - if ((g_cls_aiCallback = jniUtil_makeGlobalRef(env, g_cls_aiCallback, CLS_AI_CALLBACK)) == NULL) - return NULL; - // get (int skirmishAIId) constructor - if ((g_m_aiCallback_ctor_I = jniUtil_getMethodID(env, g_cls_aiCallback, "", "(I)V")) == NULL) - return NULL; - } - - #if 1 - // reuse callback if reloading - // this should be safe since the callback objects created in java_skirmishAI_init are not - // broken by java_skirmishAI_handleEvent, which also calls java_reattachCurrentThread and - // mightget a different env-pointer back - if (jAICallBacks[skirmishAIId] != NULL) - return jAICallBacks[skirmishAIId]; - #endif - - jobject o_clb = (*env)->NewObject(env, g_cls_aiCallback, g_m_aiCallback_ctor_I, skirmishAIId); - - if (jniUtil_checkException(env, "Failed creating Java AI Callback instance")) - return NULL; - - // return (jAICallBacks[skirmishAIId] = jniUtil_makeGlobalRef(env, o_clb, "AI callback instance")); - return (jAICallBacks[skirmishAIId] = o_clb); -} - - - -static bool java_loadSkirmishAI( - JNIEnv* env, - const char* shortName, - const char* version, - const char* className, - jobject* o_ai, - jobject* o_aiClassLoader -) { - #if 0 - // convert className from "com.myai.AI" to "com/myai/AI" - const size_t classNameP_sizeMax = strlen(className) + 1; - char classNameP[classNameP_sizeMax]; - STRCPY_T(classNameP, classNameP_sizeMax, className); - util_strReplaceChar(classNameP, '.', '/'); - #endif - - // get the AIs private class-loader - jobject o_global_aiClassLoader = java_createAIClassLoader(env, shortName, version); - - if (o_global_aiClassLoader == NULL) - return false; - - *o_aiClassLoader = o_global_aiClassLoader; - - // get the AI interface (from AIInterface.jar) - if (g_cls_ai_int == NULL) { - if ((g_cls_ai_int = jniUtil_findClass(env, INT_AI)) == NULL) - return false; - if ((g_cls_ai_int = jniUtil_makeGlobalRef(env, g_cls_ai_int, "AI interface class")) == NULL) - return false; - } - - // get the AI implementation class (from SkirmishAI.jar) - jclass cls_ai = jniUtil_findClassThroughLoader(env, o_global_aiClassLoader, className); - - if (cls_ai == NULL) - return false; - - const bool implementsAIInt = (bool) (*env)->IsAssignableFrom(env, cls_ai, g_cls_ai_int); - - if (!implementsAIInt || (*env)->ExceptionCheck(env)) { - simpleLog_logL(LOG_LEVEL_ERROR, "AI class not assignable from interface "INT_AI": %s", className); - simpleLog_logL(LOG_LEVEL_ERROR, "possible reasons (this list could be incomplete):"); - simpleLog_logL(LOG_LEVEL_ERROR, "* "INT_AI" interface not implemented"); - simpleLog_logL(LOG_LEVEL_ERROR, "* The AI is not compiled for the Java AI Interface version in use"); - - if (implementsAIInt) - (*env)->ExceptionDescribe(env); - - return false; - } - - - // get factory no-arg ctor - jmethodID m_ai_ctor = jniUtil_getMethodID(env, cls_ai, "", "()V"); - - if (m_ai_ctor == NULL) - return false; - - - // get AI instance - jobject o_local_ai = (*env)->NewObject(env, cls_ai, m_ai_ctor); - - if (o_local_ai == NULL || (*env)->ExceptionCheck(env)) { - simpleLog_logL(LOG_LEVEL_ERROR, "Failed fetching AI instance for class: %s", className); - - if (o_local_ai != NULL) - (*env)->ExceptionDescribe(env); - - return false; - } - - // make the AI a global reference, so it will not be garbage collected even after this method returns - *o_ai = jniUtil_makeGlobalRef(env, o_local_ai, "AI instance"); - return true; -} - - -bool java_initSkirmishAIClass( - const char* const shortName, - const char* const version, - const char* const className, - int skirmishAIId -) { - bool success = false; - - // see if an AI for className is instantiated already - size_t sai; - size_t firstFree = numSkirmishAIs; - - for (sai = 0; sai < numSkirmishAIs; ++sai) { - if (jAIClassNames[sai] == NULL) { - firstFree = sai; - break; - } - } - - // sai is now either the instantiated one, or a free one - // instantiate AI (if not already instantiated) - assert(sai < maxSkirmishAIs); - - if (jAIClassNames[sai] == NULL) { - sai = firstFree; - java_establishJavaEnv(); - JNIEnv* env = java_getJNIEnv(false); - - jobject instance = NULL; - jobject classLoader = NULL; - - success = java_loadSkirmishAI(env, shortName, version, className, &instance, &classLoader); - java_establishSpringEnv(); - - if (success) { - jAIInstances[sai] = instance; - jAIClassLoaders[sai] = classLoader; - jAIClassNames[sai] = util_allocStrCpy(className); - - numSkirmishAIs += (firstFree == numSkirmishAIs); - } else { - simpleLog_logL(LOG_LEVEL_ERROR, "Class loading failed for class: %s", className); - } - } else { - success = true; - } - - if (success) - skirmishAIId_skirmishAiImpl[skirmishAIId] = sai; - - return success; -} - -bool java_releaseSkirmishAIClass(const char* className) -{ - // see if an AI for className is instantiated - size_t sai; - for (sai = 0; sai < numSkirmishAIs; ++sai) { - if (jAIClassNames[sai] == NULL) - continue; - if (strcmp(jAIClassNames[sai], className) == 0) - break; - } - - // sai is now either the instantiated one, or a free one - // release AI (if its instance was found) - assert(sai < maxSkirmishAIs); - - if (jAIClassNames[sai] == NULL) - return false; - - java_establishJavaEnv(); - JNIEnv* env = java_getJNIEnv(false); - - - // delete the AI class-loader global reference, - // so it will be garbage collected - bool successPart = jniUtil_deleteGlobalRef(env, jAIClassLoaders[sai], "AI class-loader"); - bool success = successPart; - - // delete the AI global reference, - // so it will be garbage collected - successPart = jniUtil_deleteGlobalRef(env, jAIInstances[sai], "AI instance"); - success = success && successPart; - - java_establishSpringEnv(); - - if (success) { - jAIClassLoaders[sai] = NULL; - jAIInstances[sai] = NULL; - - FREE(jAIClassNames[sai]); - - // if it is the last implementation - numSkirmishAIs -= ((sai + 1) == numSkirmishAIs); - } - - return success; -} - -bool java_releaseAllSkirmishAIClasses() -{ - bool success = true; - - const char* className = ""; - size_t sai; - - for (sai = 0; sai < numSkirmishAIs; ++sai) { - if ((className = jAIClassNames[sai]) == NULL) - continue; - - success = success && java_releaseSkirmishAIClass(className); - } - - return success; -} - - - -int java_skirmishAI_init(int skirmishAIId, const struct SSkirmishAICallback* aiCallback) -{ - int res = -1; - - java_establishJavaEnv(); - - JNIEnv* env = java_getJNIEnv(false); - jobject global_javaAICallback = java_createAICallback(env, aiCallback, skirmishAIId); - - if (global_javaAICallback != NULL) - res = eventsJniBridge_initAI(env, skirmishAIId, global_javaAICallback); - - java_establishSpringEnv(); - return res; -} - -int java_skirmishAI_release(int skirmishAIId) -{ - return 0; -} - -int java_skirmishAI_handleEvent(int skirmishAIId, int topic, const void* data) -{ - java_establishJavaEnv(); - - JNIEnv* env = java_getJNIEnv(false); - const size_t sai = skirmishAIId_skirmishAiImpl[skirmishAIId]; - jobject aiInstance = jAIInstances[sai]; - const int res = eventsJniBridge_handleEvent(env, aiInstance, skirmishAIId, topic, data); - - java_establishSpringEnv(); - return res; -} diff --git a/AI/Interfaces/Java/src/main/native/JavaBridge.h b/AI/Interfaces/Java/src/main/native/JavaBridge.h deleted file mode 100644 index d742f47ef38..00000000000 --- a/AI/Interfaces/Java/src/main/native/JavaBridge.h +++ /dev/null @@ -1,73 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -#ifndef _JAVA_BRIDGE_H -#define _JAVA_BRIDGE_H - -#define JVM_PROPERTIES_FILE "jvm.properties" - -#define PKG_AI "com/springrts/ai/" -#define INT_AI PKG_AI"AI" -#define CLS_AI_CALLBACK PKG_AI"JniAICallback" - -// define path entry delimiter, used eg for the java class-path -#ifdef _WIN32 -#define ENTRY_DELIM ";" -#else -#define ENTRY_DELIM ":" -#endif -#define PATH_DELIM "/" - -#ifdef __cplusplus -extern "C" { -#endif - -#include // bool, true, false - -struct SAIInterfaceCallback; -struct SSkirmishAICallback; - -bool java_unloadJNIEnv(); - -bool java_initStatic(int interfaceId, const struct SAIInterfaceCallback* callback); -bool java_releaseStatic(); - -/** - * Instantiates an instance of the specified className. - * - * @param shortName further specifies the the AI to load - * @param version further specifies the the AI to load - * @param className fully qualified name of a Java class that implements - * interface com.springrts.ai.AI, eg: - * "com.myai.AI" - * @param teamId The team that will be using this AI. - * Multiple teams may use the same AI implementation. - * @return true, if the AI implementation is now loaded - */ -bool java_initSkirmishAIClass( - const char* const shortName, - const char* const version, - const char* const className, - int teamId -); - -/** - * Release the loaded AI specified through a class name. - * - * @param className fully qualified name of a Java class that implements - * interface com.springrts.ai.AI, eg: - * "com.myai.AI" - * @return true, if the AI implementation was loaded and is now - * successfully unloaded - */ -bool java_releaseSkirmishAIClass(const char* className); -bool java_releaseAllSkirmishAIClasses(); - -int java_skirmishAI_init(int teamId, const struct SSkirmishAICallback* callback); -int java_skirmishAI_release(int teamId); -int java_skirmishAI_handleEvent(int teamId, int topic, const void* data); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // _JAVA_BRIDGE_H diff --git a/AI/Interfaces/Java/src/main/native/JniUtil.c b/AI/Interfaces/Java/src/main/native/JniUtil.c deleted file mode 100644 index 909afc70bd3..00000000000 --- a/AI/Interfaces/Java/src/main/native/JniUtil.c +++ /dev/null @@ -1,288 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -#include "JniUtil.h" - -#include "CUtils/SimpleLog.h" - - -// JNI global vars -static jclass g_cls_url = NULL; -static jmethodID g_m_url_ctor = NULL; - -static jclass g_cls_urlClassLoader = NULL; -static jmethodID g_m_urlClassLoader_ctor = NULL; -static jmethodID g_m_urlClassLoader_findClass = NULL; - - -const char* jniUtil_getJniRetValDescription(const jint retVal) { - - switch (retVal) { - case JNI_OK: { return "JNI_OK - success"; break; } - case JNI_ERR: { return "JNI_ERR - unknown error"; break; } - case JNI_EDETACHED: { return "JNI_EDETACHED - thread detached from the VM"; break; } - case JNI_EVERSION: { return "JNI_EVERSION - JNI version error"; break; } -#ifdef JNI_ENOMEM - case JNI_ENOMEM: { return "JNI_ENOMEM - not enough (contiguous) memory"; break; } -#endif // JNI_ENOMEM -#ifdef JNI_EEXIST - case JNI_EEXIST: { return "JNI_EEXIST - VM already created"; break; } -#endif // JNI_EEXIST -#ifdef JNI_EINVAL - case JNI_EINVAL: { return "JNI_EINVAL - invalid arguments"; break; } -#endif // JNI_EINVAL - default: { return "UNKNOWN - unknown/invalid JNI return value"; break; } - } -} - -bool jniUtil_checkException(JNIEnv* env, const char* const errorMsg) { - - if ((*env)->ExceptionCheck(env)) { - simpleLog_logL(LOG_LEVEL_ERROR, errorMsg); - (*env)->ExceptionDescribe(env); - return true; - } - - return false; -} - -jclass jniUtil_findClass(JNIEnv* env, const char* const className) { - - jclass res = NULL; - - res = (*env)->FindClass(env, className); - const bool hasException = (*env)->ExceptionCheck(env); - if (res == NULL || hasException) { - simpleLog_logL(LOG_LEVEL_ERROR, "Class not found: \"%s\"", className); - if (hasException) { - (*env)->ExceptionDescribe(env); - } - res = NULL; - } - - return res; -} - -jobject jniUtil_makeGlobalRef(JNIEnv* env, jobject localObject, const char* objDesc) { - - jobject res = NULL; - - // Make the local class a global reference, - // so it will not be garbage collected, - // even after this method returned, - // but only if explicitly deleted with DeleteGlobalRef - res = (*env)->NewGlobalRef(env, localObject); - if ((*env)->ExceptionCheck(env)) { - simpleLog_logL(LOG_LEVEL_ERROR, - "Failed to make %s a global reference.", - ((objDesc == NULL) ? "" : objDesc)); - (*env)->ExceptionDescribe(env); - res = NULL; - } - - return res; -} - -bool jniUtil_deleteGlobalRef(JNIEnv* env, jobject globalObject, - const char* objDesc) { - - // delete the AI class-loader global reference, - // so it will be garbage collected - (*env)->DeleteGlobalRef(env, globalObject); - if ((*env)->ExceptionCheck(env)) { - simpleLog_logL(LOG_LEVEL_ERROR, - "Failed to delete global reference %s.", - ((objDesc == NULL) ? "" : objDesc)); - (*env)->ExceptionDescribe(env); - return false; - } - - return true; -} - -jmethodID jniUtil_getMethodID(JNIEnv* env, jclass cls, - const char* const name, const char* const signature) { - - jmethodID res = NULL; - - res = (*env)->GetMethodID(env, cls, name, signature); - const bool hasException = (*env)->ExceptionCheck(env); - if (res == NULL || hasException) { - simpleLog_logL(LOG_LEVEL_ERROR, "Method not found: %s(%s)", - name, signature); - if (hasException) { - (*env)->ExceptionDescribe(env); - } - res = NULL; - } - - return res; -} - -jmethodID jniUtil_getStaticMethodID(JNIEnv* env, jclass cls, - const char* const name, const char* const signature) { - - jmethodID res = NULL; - - res = (*env)->GetStaticMethodID(env, cls, name, signature); - const bool hasException = (*env)->ExceptionCheck(env); - if (res == NULL || hasException) { - simpleLog_logL(LOG_LEVEL_ERROR, "Method not found: %s(%s)", - name, signature); - if (hasException) { - (*env)->ExceptionDescribe(env); - } - res = NULL; - } - - return res; -} - - -static bool jniUtil_initURLClass(JNIEnv* env) { - - if (g_m_url_ctor == NULL) { - // get the URL class - static const char* const fcCls = "java/net/URL"; - - g_cls_url = jniUtil_findClass(env, fcCls); - if (g_cls_url == NULL) return false; - - g_cls_url = jniUtil_makeGlobalRef(env, g_cls_url, fcCls); - if (g_cls_url == NULL) return false; - - // get (String) constructor - g_m_url_ctor = jniUtil_getMethodID(env, g_cls_url, - "", "(Ljava/lang/String;)V"); - if (g_m_url_ctor == NULL) return false; - } - - return true; -} -jobject jniUtil_createURLObject(JNIEnv* env, const char* const url) { - - jobject jurl = NULL; - - bool ok = true; - if (g_cls_url == NULL) { - ok = jniUtil_initURLClass(env); - } - - if (ok) { - jstring jstrUrl = (*env)->NewStringUTF(env, url); - if (jniUtil_checkException(env, "Failed creating Java String.")) { jstrUrl = NULL; } - if (jstrUrl != NULL) { - jurl = (*env)->NewObject(env, g_cls_url, g_m_url_ctor, jstrUrl); - if (jniUtil_checkException(env, "Failed creating Java URL.")) { jurl = NULL; } - } - } else { - simpleLog_logL(LOG_LEVEL_ERROR, - "Failed creating Java URL; URL class not initialized."); - } - - return jurl; -} -jobjectArray jniUtil_createURLArray(JNIEnv* env, size_t size) { - - jobjectArray jurlArr = NULL; - - bool ok = true; - if (g_cls_url == NULL) { - ok = jniUtil_initURLClass(env); - } - - if (ok) { - jurlArr = (*env)->NewObjectArray(env, size, g_cls_url, NULL); - if (jniUtil_checkException(env, "Failed creating URL[].")) { jurlArr = NULL; } - } else { - simpleLog_logL(LOG_LEVEL_ERROR, - "Failed creating Java URL[]; URL class not initialized."); - } - - return jurlArr; -} -bool jniUtil_insertURLIntoArray(JNIEnv* env, jobjectArray arr, size_t index, jobject url) { - - bool ok = true; - - (*env)->SetObjectArrayElement(env, arr, index, url); - if (jniUtil_checkException(env, "Failed inserting Java URL into array.")) { ok = false; } - - return ok; -} - -static bool jniUtil_initURLClassLoaderClass(JNIEnv* env) { - - if (g_m_urlClassLoader_findClass == NULL) { - // get the URLClassLoader class - static const char* const fcCls = "java/net/URLClassLoader"; - - g_cls_urlClassLoader = jniUtil_findClass(env, fcCls); - if (g_cls_urlClassLoader == NULL) return false; - - g_cls_urlClassLoader = - jniUtil_makeGlobalRef(env, g_cls_urlClassLoader, fcCls); - if (g_cls_urlClassLoader == NULL) return false; - - // get (URL[]) constructor - g_m_urlClassLoader_ctor = jniUtil_getMethodID(env, - g_cls_urlClassLoader, "", "([Ljava/net/URL;)V"); - if (g_m_urlClassLoader_ctor == NULL) return false; - - // get the findClass(String) method - g_m_urlClassLoader_findClass = jniUtil_getMethodID(env, - g_cls_urlClassLoader, "findClass", - "(Ljava/lang/String;)Ljava/lang/Class;"); - if (g_m_urlClassLoader_findClass == NULL) return false; - } - - return true; -} -jobject jniUtil_createURLClassLoader(JNIEnv* env, jobject urlArray) { - - jobject classLoader = NULL; - - bool ok = true; - if (g_m_urlClassLoader_ctor == NULL) { - ok = jniUtil_initURLClassLoaderClass(env); - } - - if (ok) { - classLoader = (*env)->NewObject(env, g_cls_urlClassLoader, g_m_urlClassLoader_ctor, urlArray); - if (jniUtil_checkException(env, "Failed creating class-loader.")) { return NULL; } - } else { - simpleLog_logL(LOG_LEVEL_ERROR, - "Failed creating class-loader; class-loader class not initialized."); - } - - return classLoader; -} -jclass jniUtil_findClassThroughLoader(JNIEnv* env, jobject classLoader, const char* const className) { - - jclass cls = NULL; - - bool ok = true; - if (g_m_urlClassLoader_findClass == NULL) { - ok = jniUtil_initURLClassLoaderClass(env); - } - - if (ok) { - //cls = (*env)->FindClass(env, classNameP); - jstring jstr_className = (*env)->NewStringUTF(env, className); - cls = (*env)->CallObjectMethod(env, classLoader, g_m_urlClassLoader_findClass, jstr_className); - const bool hasException = (*env)->ExceptionCheck(env); - if (cls == NULL || hasException) { - simpleLog_logL(LOG_LEVEL_ERROR, - "Class not found \"%s\"", className); - if (hasException) { - (*env)->ExceptionDescribe(env); - } - cls = NULL; - } - } else { - simpleLog_logL(LOG_LEVEL_ERROR, - "Failed finding class; class-loader class not initialized."); - } - - return cls; -} - diff --git a/AI/Interfaces/Java/src/main/native/JniUtil.h b/AI/Interfaces/Java/src/main/native/JniUtil.h deleted file mode 100644 index 336f2fc4f1e..00000000000 --- a/AI/Interfaces/Java/src/main/native/JniUtil.h +++ /dev/null @@ -1,80 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -#ifndef _JNI_UTIL_H -#define _JNI_UTIL_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include // bool, true, false - -#include - -/** - * Takes a JNI function return value, and returns a short description for it. - * This can be used for getting a human readable string describing the - * return value of functions like AttachCurrentThread() and CreateJavaVM(). - * - * @return a short description for a JNI function return value - */ -const char* jniUtil_getJniRetValDescription(const jint retVal); - -/** - * Handles a possible JNI/Java exception. - * In case an exception is present, the supplied error message is written to - * the log file, and the whole exception info is written to stderr. - * - * @return true, if there was an exception - */ -bool jniUtil_checkException(JNIEnv* env, const char* const errorMsg); - -/** - * Tries to find a Java class. - * - * @return a local reference to the class if found, NULL otherwise - */ -jclass jniUtil_findClass(JNIEnv* env, const char* const className); - -/** - * Converts a local to a global reference. - * As jclass inherits from jobject, this can be used for jclass too. - * - * @return the global reference to localObject on success, NULL otherwise - */ -jobject jniUtil_makeGlobalRef(JNIEnv* env, jobject localObject, const char* objDesc); - -/** - * Deletes a global reference to an object. - * As jclass inherits from jobject, this can be used for jclass too. - * - * @return true on success, false if an error occurred - */ -bool jniUtil_deleteGlobalRef(JNIEnv* env, jobject globalObject, const char* objDesc); - -/** - * Retrieves a reference ID for calling a Java object method. - * - * @return the method ID on success, NULL otherwise - */ -jmethodID jniUtil_getMethodID(JNIEnv* env, jclass cls, const char* const name, const char* const signature); - -/** - * Retrieves a reference ID for calling a Java static method. - * - * @return the method ID on success, NULL otherwise - */ -jmethodID jniUtil_getStaticMethodID(JNIEnv* env, jclass cls, const char* const name, const char* const signature); - -jobject jniUtil_createURLObject(JNIEnv* env, const char* const url); -jobjectArray jniUtil_createURLArray(JNIEnv* env, size_t size); -bool jniUtil_insertURLIntoArray(JNIEnv* env, jobjectArray arr, size_t index, jobject url); - -jobject jniUtil_createURLClassLoader(JNIEnv* env, jobject urlArray); -jclass jniUtil_findClassThroughLoader(JNIEnv* env, jobject classLoader, const char* const className); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // _JNI_UTIL_H diff --git a/AI/Interfaces/Java/src/main/native/JvmLocater.h b/AI/Interfaces/Java/src/main/native/JvmLocater.h deleted file mode 100644 index 5032991d6a0..00000000000 --- a/AI/Interfaces/Java/src/main/native/JvmLocater.h +++ /dev/null @@ -1,53 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -#ifndef _JVM_LOCATER_H -#define _JVM_LOCATER_H - -#if !defined bool -#include -#endif -#if !defined size_t -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Returns the arch dir name, used eg here: - * .../jre/lib/${arch}/client/libjvm.so - * - * @return sparc, sparcv9, i386, amd64 or ia64 (windows only) - */ -const char* GetArchPath(); - -/* - * Given a JRE location and a JVM type, construct what the name the - * JVM shared library will be. - * - * @param jvmType "/", "\\", "client", "server" - * @param arch see GetArchPath(), use NULL for the default value - * @return true, if the JVM library was found, false otherwise. - */ -bool GetJVMPath(const char* jrePath, const char* jvmType, - char* jvmPath, size_t jvmPathSize, const char* arch); - -/** - * Find the path to a JRE install dir, using platform dependent means. - * - * @param path path of the JRE installation - * @param pathSize size of the path parameter - * @param configFile path to a simple text file containing only a path to the - * JRE installation to use, or NULL - * @param arch see GetArchPath(), use NULL for the default value - * @return true, if a JRE was found, false otherwise. - */ -bool GetJREPath(char* path, size_t pathSize, const char* configFile, - const char* arch); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // _JVM_LOCATER_H diff --git a/AI/Interfaces/Java/src/main/native/JvmLocater_common.c b/AI/Interfaces/Java/src/main/native/JvmLocater_common.c deleted file mode 100644 index a85f9d87a95..00000000000 --- a/AI/Interfaces/Java/src/main/native/JvmLocater_common.c +++ /dev/null @@ -1,188 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -#include -#include -#include -#include -#include - -#include "CUtils/Util.h" - -#include "JvmLocater.h" - -#define MAXPATHLEN 2048 -#define JRE_PATH_PROPERTY "jre.path" - -#include // for CHAR_BIT -#define CURRENT_DATA_MODEL (CHAR_BIT * sizeof(void*)) - -#include "CUtils/SimpleLog.h" -#include "System/MainDefines.h" -#include "System/SafeCStrings.h" - -// implemented in the OS specific files -const char* GetArchPath(); -bool GetJREPathFromBase(char* path, size_t pathSize, const char* basePath, - const char* arch); -bool GetJREPathOSSpecific(char* path, size_t pathSize, const char* arch); - -bool FileExists(const char* filePath) -{ - struct stat s; - return (stat(filePath, &s) == 0); -} - -//#define LOC_PROP_FILE -bool GetJREPathFromConfig(char* path, size_t pathSize, const char* configFile) -{ -#if defined LOC_PROP_FILE - // assume the config file is in properties file format - // and that the property JRE_PATH_PROPERTY contains - // the absolute path to the JRE to use - static const size_t props_sizeMax = 64; - - const char* props_keys[props_sizeMax]; - const char* props_values[props_sizeMax]; - - const size_t props_size = util_parsePropertiesFile(configFile, props_keys, - props_values, props_sizeMax); - - const char* jvmLocation = util_map_getValueByKey( - props_size, props_keys, props_values, - JRE_PATH_PROPERTY); - - if (jvmLocation == NULL) { - simpleLog_logL(LOG_LEVEL_DEBUG, "JRE not found in config file!"); - return false; - } else { - simpleLog_logL(LOG_LEVEL_NOTICE, "JRE found in config file!"); - STRCPY_T(path, pathSize, jvmLocation); - return true; - } -#else // defined LOC_PROP_FILE - // assume the config file is a plain text file containing nothing but - // the absolute path to the JRE to use in the first line - - bool found = false; - - FILE* cfp = fopen(configFile, "r"); - if (cfp == NULL) { - return found; - } - - // parse results - static const size_t line_sizeMax = 1024; - char line[line_sizeMax]; - if (fgets(line, line_sizeMax, cfp)) { - size_t line_size = strlen(line); - if (*(line+line_size-1) == '\n') { - // remove trailing '\n' - *(line+line_size-1) = '\0'; - line_size--; - } - - simpleLog_logL(LOG_LEVEL_NOTICE, - "Fetched JRE location from \"%s\"!", configFile); - - if (line_size > 0 && *line == '/') { - *(line+line_size-1) = '\0'; // remove trailing '/' - } - STRCPY_T(path, pathSize, line); - found = true; - } - fclose(cfp); - - return found; -#endif // defined LOC_PROP_FILE -} - - -bool GetJREPathFromEnvVars(char* path, size_t pathSize, const char* arch) -{ - bool found = false; - - static const size_t possLoc_sizeMax = 32; - char* possLoc[possLoc_sizeMax]; - size_t possLoc_i = 0; - - possLoc[possLoc_i++] = util_allocStrCpy("JAVA_HOME"); - possLoc[possLoc_i++] = util_allocStrCpy("JDK_HOME"); - possLoc[possLoc_i++] = util_allocStrCpy("JRE_HOME"); - - size_t l; - for (l=0; l < possLoc_i; ++l) { - const char* envPath = getenv(possLoc[l]); - if (envPath != NULL) { - found = GetJREPathFromBase(path, pathSize, envPath, arch); - if (found) { - simpleLog_logL(LOG_LEVEL_NOTICE, "JRE found in env var \"%s\"!", possLoc[l]); - goto locSearchEnd; - } else { - simpleLog_logL(LOG_LEVEL_WARNING, "Unusable JRE from env var \"%s\"=\"%s\"!", possLoc[l], envPath); - } - } - } - locSearchEnd: - - // cleanup - for (l=0; l < possLoc_i; ++l) { - free(possLoc[l]); - possLoc[l] = NULL; - } - - return found; -} - - - -bool GetJREPath(char* path, size_t pathSize, const char* configFile, - const char* arch) -{ - bool found = false; - - if (arch == NULL) { - arch = GetArchPath(); - } - - // check if a JRE location is specified in the config file - if (!found && configFile != NULL) { - found = GetJREPathFromConfig(path, pathSize, configFile); - } - - // check if a JRE is specified in an ENV var (eg. JAVA_HOME) - if (!found) { - found = GetJREPathFromEnvVars(path, pathSize, arch); - } - - // check if a JRE is located in a common location - if (!found) { - found = GetJREPathOSSpecific(path, pathSize, arch); - } - - return found; -} - -int main(int argc, const char* argv[]) { - - //simpleLog_init(NULL, false, LOG_LEVEL_DEBUG, false); - - static const size_t path_sizeMax = 1024; - char path[path_sizeMax]; - bool found = GetJREPath(path, path_sizeMax, NULL, NULL); - if (found) { - printf("JRE found: %s\n", path); - - static const size_t jvmPath_sizeMax = 1024; - char jvmPath[jvmPath_sizeMax]; - bool jvmFound = GetJVMPath(path, "client", jvmPath, jvmPath_sizeMax, NULL); - if (jvmFound) { - printf("JVM found: %s\n", jvmPath); - } else { - printf("JVM not found.\n"); - } - } else { - printf("JRE not found.\n"); - } - - return 0; -} diff --git a/AI/Interfaces/Java/src/main/native/JvmLocater_common.h b/AI/Interfaces/Java/src/main/native/JvmLocater_common.h deleted file mode 100644 index 45d81c2b9ac..00000000000 --- a/AI/Interfaces/Java/src/main/native/JvmLocater_common.h +++ /dev/null @@ -1,38 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -#ifndef _JVM_LOCATER_COMMON_H -#define _JVM_LOCATER_COMMON_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "JvmLocater.h" - -#include -#include -#include -#include -#include -#include // for CHAR_BIT - -#include "CUtils/Util.h" -#include "CUtils/SimpleLog.h" - -#define MAXPATHLEN 2048 -#define JRE_PATH_PROPERTY "jre.path" -#define CURRENT_DATA_MODEL (CHAR_BIT * sizeof(void*)) - - -bool FileExists(const char* filePath); - -bool GetJREPathFromEnvVars(char* path, size_t pathSize, const char* arch); - -bool GetJREPath(char* path, size_t pathSize, const char* configFile, - const char* arch); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // _JVM_LOCATER_COMMON_H diff --git a/AI/Interfaces/Java/src/main/native/JvmLocater_linux.c b/AI/Interfaces/Java/src/main/native/JvmLocater_linux.c deleted file mode 100644 index ef1896be34b..00000000000 --- a/AI/Interfaces/Java/src/main/native/JvmLocater_linux.c +++ /dev/null @@ -1,266 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -#if !defined _WIN32 - -#include "JvmLocater_common.h" - -#include "System/MainDefines.h" -#include "System/SafeCStrings.h" - -#include - - -#if defined APPLE -#define JVM_LIB "libjvm.dylib" -#else -#define JVM_LIB "libjvm.so" -#endif - -#if defined APPLE -#define JAVA_LIB "libjava.dylib" -#else -#define JAVA_LIB "libjava.so" -#endif - -#ifdef SPARC -# define LIBARCH32NAME "sparc" -# define LIBARCH64NAME "sparcv9" -#else // not SPARC -> LINUX or APPLE -# define LIBARCH32NAME "i386" -# define LIBARCH64NAME "amd64" -#endif // SPARC -#if defined __arch64__ -# define LIBARCHNAME LIBARCH64NAME -#else // defined __arch64__ -# define LIBARCHNAME LIBARCH32NAME -#endif // defined __arch64__ -//LIBARCH32 solaris only: sparc or i386 -//LIBARCH64 solaris only: sparcv9 or amd64 -//LIBARCH sparc, sparcv9, i386, amd64, or ia64 // last one is windows only - - -const char* GetArchPath() -{ - switch(CURRENT_DATA_MODEL) { -#ifdef DUAL_MODE - case 32: - return LIBARCH32NAME; - case 64: - return LIBARCH64NAME; -#endif // DUAL_MODE - default: - return LIBARCHNAME; - } -} - -/* - * On Solaris VM choosing is done by the launcher (java.c). - */ -bool GetJVMPath(const char* jrePath, const char* jvmType, - char* jvmPath, size_t jvmPathSize, const char* arch) -{ - if (arch == NULL) { - arch = GetArchPath(); - } - - if (*jvmType == '/') { - SNPRINTF(jvmPath, jvmPathSize, "%s/"JVM_LIB, jvmType); - } else { - SNPRINTF(jvmPath, jvmPathSize, "%s/lib/%s/%s/"JVM_LIB, jrePath, arch, - jvmType); - } - - return FileExists(jvmPath); -} - - -static bool CheckIfJREPath(const char* path, const char* arch) -{ - bool found = false; - - if (path != NULL) { - char libJava[MAXPATHLEN]; - - // Is path a JRE path? - SNPRINTF(libJava, MAXPATHLEN, "%s/lib/%s/"JAVA_LIB, path, arch); - if (access(libJava, F_OK) == 0) { - found = true; - } - } - - return found; -} - -bool GetJREPathFromBase(char* path, size_t pathSize, const char* basePath, - const char* arch) -{ - bool found = false; - - if (basePath != NULL) { - char jrePath[MAXPATHLEN]; - - // Is basePath a JRE path? - STRCPY_T(jrePath, MAXPATHLEN, basePath); - if (CheckIfJREPath(jrePath, arch)) { - STRCPY_T(path, pathSize, basePath); - found = true; - } - - // Is basePath/jre a JRE path? - STRCAT_T(jrePath, MAXPATHLEN, "/jre"); - if (CheckIfJREPath(jrePath, arch)) { - STRCPY_T(path, pathSize, jrePath); - found = true; - } - } - - return found; -} - -static size_t ExecFileSystemGlob(char** pathHits, size_t pathHits_sizeMax, - const char* globPattern) -{ - size_t pathHits_size = 0; - - // assemble the command - static const size_t cmd_sizeMax = 512; - char cmd[cmd_sizeMax]; - SNPRINTF(cmd, cmd_sizeMax, "find %s/ -maxdepth 0 2> /dev/null", globPattern); - - // execute - FILE* cmd_fp = popen(cmd, "r"); - if (cmd_fp == NULL) { - return pathHits_size; - } - - // parse results - static const size_t line_sizeMax = 512; - char line[line_sizeMax]; - while (fgets(line, line_sizeMax, cmd_fp) && (pathHits_size < pathHits_sizeMax)) { - size_t line_size = strlen(line); - if (*(line+line_size-1) == '\n') { - // remove trailing '\n' - *(line+line_size-1) = '\0'; - line_size--; - } - - simpleLog_logL(LOG_LEVEL_DEBUG, - "glob-hit \"%s\"!", line); - - if (line_size > 0 && *line == '/') { - *(line+line_size-1) = '\0'; // remove trailing '/' - pathHits[pathHits_size++] = util_allocStrCpy(line); - } - } - pclose(cmd_fp); - - return pathHits_size; -} -static bool GetJREPathInCommonLocations(char* path, size_t pathSize, const char* arch) -{ - bool found = false; - - static const size_t possLoc_sizeMax = 32; - char* possLoc[possLoc_sizeMax]; - size_t possLoc_i = 0; - - possLoc[possLoc_i++] = util_allocStrCpy("/usr/local/jdk*"); - possLoc[possLoc_i++] = util_allocStrCpy("/usr/lib/jvm/default-java"); - possLoc[possLoc_i++] = util_allocStrCpy("/usr/lib/jvm/java-?-sun"); - possLoc[possLoc_i++] = util_allocStrCpy("/usr/lib/jvm/java-?-*"); - possLoc[possLoc_i++] = util_allocStrCpy("~/jdk*"); - possLoc[possLoc_i++] = util_allocStrCpy("~/bin/jdk*"); - possLoc[possLoc_i++] = util_allocStrCpy("~/jre*"); - possLoc[possLoc_i++] = util_allocStrCpy("~/bin/jre*"); - - static const size_t globHits_sizeMax = 32; - char* globHits[globHits_sizeMax]; - size_t l, g; - for (l=0; l < possLoc_i; ++l) { - const size_t globHits_size = ExecFileSystemGlob(globHits, globHits_sizeMax, possLoc[l]); - for (g=0; g < globHits_size; ++g) { - found = GetJREPathFromBase(path, pathSize, globHits[g], arch); - if (found) { - simpleLog_logL(LOG_LEVEL_NOTICE, - "JRE found common location env var \"%s\"!", - possLoc[l]); - goto locSearchEnd; - } - } - } - locSearchEnd: - - // cleanup - for (l=0; l < possLoc_i; ++l) { - free(possLoc[l]); - possLoc[l] = NULL; - } - - return found; -} - -static bool GetJREPathWhichJava(char* path, size_t pathSize, const char* arch) -{ - static const char* suf = "/bin/java"; - const size_t suf_size = strlen(suf); - - bool found = false; - - // execute - FILE* cmd_fp = popen("which java | sed 's/[\\n\\r]/K/g'", "r"); - if (cmd_fp == NULL) { - return found; - } - - // parse results - static const size_t line_sizeMax = 512; - char line[line_sizeMax]; - if (fgets(line, line_sizeMax, cmd_fp)) { - if (*line == '/') { // -> absolute path - size_t line_size = strlen(line); - if (*(line+line_size-1) == '\n') { - // remove trailing '\n' - *(line+line_size-1) = '\0'; - line_size--; - } - - simpleLog_logL(LOG_LEVEL_DEBUG, - "which line \"%s\"!", line); - - if (line_size > suf_size - && strcmp(line+(line_size-suf_size), suf) == 0) { - // line ends with suf - simpleLog_logL(LOG_LEVEL_NOTICE, - "JRE found with `which java`!"); - // remove suf - *(line+(line_size-suf_size)) = '\0'; - found = GetJREPathFromBase(path, pathSize, line, arch); - } - } - } - pclose(cmd_fp); - - return found; -} - -/* - * Find path to JRE based on .exe's location or registry settings. - */ -bool GetJREPathOSSpecific(char* path, size_t pathSize, const char* arch) -{ - bool found = false; - - // check if a JRE is located in a common location - if (!found) { - found = GetJREPathInCommonLocations(path, pathSize, arch); - } - - // check if a JRE is located in a common location - if (!found) { - found = GetJREPathWhichJava(path, pathSize, arch); - } - - return found; -} - -#endif // !defined _WIN32 diff --git a/AI/Interfaces/Java/src/main/native/JvmLocater_windows.c b/AI/Interfaces/Java/src/main/native/JvmLocater_windows.c deleted file mode 100644 index 6bb5367f2b6..00000000000 --- a/AI/Interfaces/Java/src/main/native/JvmLocater_windows.c +++ /dev/null @@ -1,181 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -#if defined _WIN32 - -#include "JvmLocater_common.h" - -#include "System/MainDefines.h" -#include "System/SafeCStrings.h" - -#include -#include -#include - - -#define JVM_LIB "jvm.dll" - -#define JAVA_LIB "java.dll" - -/* - * Returns the arch path, to get the current arch use the - * macro GetArch, nbits here is ignored for now. - */ -const char* GetArchPath() -{ -#ifdef _M_AMD64 - return "amd64"; -#elif defined(_M_IA64) - return "ia64"; -#else - return "i386"; -#endif -} - -/* - * Given a JRE location and a JVM type, construct what the name the - * JVM shared library will be. Return true, if such a library - * exists, false otherwise. - */ -bool GetJVMPath(const char* jrePath, const char* jvmType, - char* jvmPath, size_t jvmPathSize, const char* arch) -{ - if (arch == NULL) { - arch = GetArchPath(); - } - - if ((*jvmType == '/') || (*jvmType == '\\')) { - SNPRINTF(jvmPath, jvmPathSize, "%s\\"JVM_LIB, jvmType); - } else { - SNPRINTF(jvmPath, jvmPathSize, "%s\\bin\\%s\\"JVM_LIB, jrePath, - jvmType); - } - - return FileExists(jvmPath); -} - -static bool CheckIfJREPath(const char* path, const char* arch) -{ - bool found = false; - - if (path != NULL) { - char libJava[MAXPATHLEN]; - - // Is path a JRE path? - SNPRINTF(libJava, MAXPATHLEN, "%s\\bin\\"JAVA_LIB, path); - if (FileExists(libJava)) { - found = true; - } - } - - return found; -} - -bool GetJREPathFromBase(char* path, size_t pathSize, const char* basePath, - const char* arch) -{ - bool found = false; - - if (basePath != NULL) { - //if (GetApplicationHome(path, pathSize)) { - char jrePath[MAXPATHLEN]; - - // Is basePath a JRE path? - STRCPY_T(jrePath, MAXPATHLEN, basePath); - if (CheckIfJREPath(jrePath, arch)) { - STRCPY_T(path, pathSize, basePath); - found = true; - } - - // Is basePath/jre a JRE path? - STRCAT_T(jrePath, MAXPATHLEN, "\\jre"); - if (CheckIfJREPath(jrePath, arch)) { - STRCPY_T(path, pathSize, jrePath); - found = true; - } - } - - return found; -} - -/* - * Helpers to look in the registry for a public JRE. - */ - -/* Same for 1.5.0, 1.5.1, 1.5.2 etc. */ -#define JRE_REG_KEY "Software\\JavaSoft\\Java Runtime Environment" - -static bool GetStringFromRegistry(HKEY key, const char* name, char* buf, - size_t bufSize) -{ - DWORD type, size; - - if (RegQueryValueEx(key, name, 0, &type, 0, &size) == 0 - && type == REG_SZ - && (size < bufSize)) { - if (RegQueryValueEx(key, name, 0, 0, (LPBYTE)buf, &size) == 0) { - return true; - } - } - return false; -} - -static bool GetJREPathFromRegistry(char* path, size_t pathSize, const char* arch) -{ - HKEY key, subkey; - char version[MAXPATHLEN]; - - /* - * Note: There is a very similar implementation of the following - * registry reading code in the Windows java control panel (javacp.cpl). - * If there are bugs here, a similar bug probably exists there. Hence, - * changes here require inspection there. - */ - - // Find the current version of the JRE - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, JRE_REG_KEY, 0, KEY_READ, &key) != 0) { - return false; - } - - if (!GetStringFromRegistry(key, "CurrentVersion", - version, sizeof(version))) { - RegCloseKey(key); - return false; - } - - /*if (strcmp(version, wantedVersion) != 0) { - RegCloseKey(key); - return false; - }*/ - - // Find directory where the current version is installed. - if (RegOpenKeyEx(key, version, 0, KEY_READ, &subkey) != 0) { - RegCloseKey(key); - return false; - } - - if (!GetStringFromRegistry(subkey, "JavaHome", path, pathSize)) { - RegCloseKey(key); - RegCloseKey(subkey); - return false; - } - - RegCloseKey(key); - RegCloseKey(subkey); - - simpleLog_logL(LOG_LEVEL_NOTICE, "JRE found in registry!"); - return true; -} - -bool GetJREPathOSSpecific(char* path, size_t pathSize, const char* arch) -{ - bool found = false; - - // check if a JRE is specified in the registry - if (!found) { - found = GetJREPathFromRegistry(path, pathSize, arch); - } - - return found; -} - -#endif // defined _WIN32 diff --git a/AI/Skirmish/NullJavaAI/CMakeLists.txt b/AI/Skirmish/NullJavaAI/CMakeLists.txt deleted file mode 100644 index ffac49c05b4..00000000000 --- a/AI/Skirmish/NullJavaAI/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -### Generic Java Skirmish AI config -# - -configure_java_skirmish_ai("") diff --git a/AI/Skirmish/NullJavaAI/VERSION b/AI/Skirmish/NullJavaAI/VERSION deleted file mode 100644 index ceab6e11ece..00000000000 --- a/AI/Skirmish/NullJavaAI/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.1 \ No newline at end of file diff --git a/AI/Skirmish/NullJavaAI/bin/ant.dependent.properties b/AI/Skirmish/NullJavaAI/bin/ant.dependent.properties deleted file mode 100644 index 559cacc4e54..00000000000 --- a/AI/Skirmish/NullJavaAI/bin/ant.dependent.properties +++ /dev/null @@ -1,36 +0,0 @@ -# properties file for spring Java Skirmish AIs -# This is used when you do not use the spring repository for compiling. -# -# Paths are relative to the project home (which is ../ from this file). -# All values are optional, and listed here with their defaults. -# -# The default assumes that you have the spring source, -# and do a spring in-source build as it is the default with CMake and SCons, -# and as the buildbot does it. -# -# build-dir: ${spring.home}/AI/Skirmish/${ai.name}/ -# install-dir: ${spring.home}/dist/AI/Skirmish/${ai.name}/${ai.version}/ -# -# This file is loaded from within build.xml, -# but only if no "jlibs-interface/" dir is present. -# - -;spring.home=../../.. - -# This is used only in the next property -;build.home=${spring.home} -# Where jar files will be built -;build.dir=${build.home}/AI/Skirmish/${skirmish.ai.name} - -# This is used only in the next property -;dist.home=${spring.home}/dist -# Where jar files will be installed to -;dist.dir=${dist.home}/AI/Skirmish/${skirmish.ai.name}/${skirmish.ai.version} -# Where HTML JavaDoc files will be generated -;doc.dir=${dist.home}/AI/Skirmish/${skirmish.ai.name}/${skirmish.ai.version}/doc/jdoc - -# The following two are needed for compiling (to create the classpath) -# * Here we look for ./jlib/*.jar recursively -;ai.interface.src.home=${spring.home}/AI/Interfaces/Java -# * Here we look for AIInterface.jar -;ai.interface.build.home=${build.home}/AI/Interfaces/Java diff --git a/AI/Skirmish/NullJavaAI/bin/ant.independent.properties b/AI/Skirmish/NullJavaAI/bin/ant.independent.properties deleted file mode 100644 index 129a1589a21..00000000000 --- a/AI/Skirmish/NullJavaAI/bin/ant.independent.properties +++ /dev/null @@ -1,29 +0,0 @@ -# properties file for spring Java Skirmish AIs -# This is used when you do not use the spring repository for compiling. -# -# Paths are relative to the project home (which is ../ from this file). -# All values are optional, and listed here with their defaults. -# -# For the project, you do not need the spring sources, -# but you will need the "jlibs-interface/" instead, which contains -# the Java AI Interfaces jlibs plus its binary and source jar. -# -# build-dir: build/ -# install-dir: dist/ -# -# This file is loaded from within build.xml, -# but only if a "jlibs-interface/" dir is present. -# - -# This is used only in the next property -;build.home=build -# Where jar files will be built -;build.dir=${build.home} - -# This is used only in the next property. -# You will want to set dist.home to your spring install dir. -;dist.home=dist -# Where jar files will be installed to -;dist.dir=${dist.home}/AI/Skirmish/${skirmish.ai.name}/${skirmish.ai.version} -# Where HTML JavaDoc files will be generated -;doc.dir=${dist.home}/AI/Skirmish/${skirmish.ai.name}/${skirmish.ai.version}/doc/jdoc diff --git a/AI/Skirmish/NullJavaAI/bin/build.xml b/AI/Skirmish/NullJavaAI/bin/build.xml deleted file mode 100644 index 8d3b185b6bf..00000000000 --- a/AI/Skirmish/NullJavaAI/bin/build.xml +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - . -File not found: ${ai.interface.jar} -Please make sure to compile the Java AI Interface -before trying to compile this AI, or request -an independent version of this project from -wherever you got this AI from. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Successfully bumped ${skirmish.ai.name} version from ${skirmish.ai.version} to ${skirmish.ai.version.new}. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - . -Please specify either home.new or name.new, for example: - ant create-independent -Dhome.new=~/projects/SpringSkirmishAIs/ -or - ant create-independent -Dname.new=MyJavaAI - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Spring source independant Java Skirmish AI project copied to: - ${project.dir.new} - -You may want to change your java source package structure -and the data/AIInto.lua file for the new project. - - - - - - - - - - - - - - - - - diff --git a/AI/Skirmish/NullJavaAI/data/AIInfo.lua b/AI/Skirmish/NullJavaAI/data/AIInfo.lua deleted file mode 100644 index 75f19a8632b..00000000000 --- a/AI/Skirmish/NullJavaAI/data/AIInfo.lua +++ /dev/null @@ -1,53 +0,0 @@ --- --- Info Definition Table format --- --- --- These keywords must be lowercase for LuaParser to read them. --- --- key: user defined or one of the SKIRMISH_AI_PROPERTY_* defines in --- SSkirmishAILibrary.h --- value: the value of the property --- desc: the description (could be used as a tooltip) --- --- --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -local infos = { - { - key = 'shortName', - value = 'NullJavaAI', - desc = 'machine conform name.', - }, - { - key = 'version', - value = '0.1', -- AI version - !This comment is used for parsing! - }, - { - key = 'className', - value = 'nulljavaai.NullJavaAI', - desc = 'fully qualified name of a class that implements interface com.springrts.ai.AI', - }, - { - key = 'name', - value = 'low-level Java test Skirmish AI', - desc = 'human readable name.', - }, - { - key = 'loadSupported', - value = 'no', - desc = 'whether this AI supports loading or not', - }, - { - key = 'interfaceShortName', - value = 'Java', -- AI Interface name - !This comment is used for parsing! - desc = 'the shortName of the AI interface this AI needs', - }, - { - key = 'interfaceVersion', - value = '0.1', -- AI Interface version - !This comment is used for parsing! - desc = 'the minimum version of the AI interface required by this AI', - }, -} - -return infos diff --git a/AI/Skirmish/NullJavaAI/data/AIOptions.lua b/AI/Skirmish/NullJavaAI/data/AIOptions.lua deleted file mode 100644 index d435d62e964..00000000000 --- a/AI/Skirmish/NullJavaAI/data/AIOptions.lua +++ /dev/null @@ -1,15 +0,0 @@ --- --- Custom Options Definition Table format --- --- A detailed example of how this format works can be found --- in the spring source under: --- AI/Skirmish/NullAI/data/AIOptions.lua --- --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -local options = { -} - -return options - diff --git a/AI/Skirmish/NullJavaAI/manifest.mf b/AI/Skirmish/NullJavaAI/manifest.mf deleted file mode 100755 index 59499bce4a2..00000000000 --- a/AI/Skirmish/NullJavaAI/manifest.mf +++ /dev/null @@ -1,2 +0,0 @@ -Manifest-Version: 1.0 - diff --git a/AI/Skirmish/NullJavaAI/src/nulljavaai/NullJavaAI.java b/AI/Skirmish/NullJavaAI/src/nulljavaai/NullJavaAI.java deleted file mode 100644 index 41c42c6c235..00000000000 --- a/AI/Skirmish/NullJavaAI/src/nulljavaai/NullJavaAI.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - Copyright (c) 2008 Robin Vobruba - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package nulljavaai; - - -import com.springrts.ai.*; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.logging.*; - -/** - * Serves as Interface for a Java Skirmish AIs for the Spring engine. - * - * @author hoijui - * @version 0.1 - */ -public class NullJavaAI extends AbstractAI implements AI { - - private int skirmishAIId = -1; - private AICallback clb = null; - private String myLogFile = null; - private Logger log = null; - private int frame = -1; - - private static class MyCustomLogFormatter extends Formatter { - - private DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss:SSS dd.MM.yyyy"); - - public String format(LogRecord record) { - - // Create a StringBuffer to contain the formatted record - // start with the date. - StringBuffer sb = new StringBuffer(); - - // Get the date from the LogRecord and add it to the buffer - Date date = new Date(record.getMillis()); - sb.append(dateFormat.format(date)); - sb.append(" "); - - // Get the level name and add it to the buffer - sb.append(record.getLevel().getName()); - sb.append(": "); - - // Get the formatted message (includes localization - // and substitution of parameters) and add it to the buffer - sb.append(formatMessage(record)); - sb.append("\n"); - - return sb.toString(); - } - } - - public static boolean isDebugging() { - return true; - } - - public NullJavaAI() {} - - - @Override - public int init(int skirmishAIId, AICallback callback) { - - int ret = -1; - - this.clb = callback; - - // initialize the log - try { - int teamId = clb.SkirmishAI_getTeamId(); - myLogFile = callback.DataDirs_locatePath("log-team-" + teamId + "-" + skirmishAIId + ".txt", true, true, false, false); - FileHandler fileLogger = new FileHandler(myLogFile, false); - fileLogger.setFormatter(new MyCustomLogFormatter()); - fileLogger.setLevel(Level.ALL); - log = Logger.getLogger("nulljavaai"); - if (isDebugging()) { - log.setLevel(Level.ALL); - } else { - log.setLevel(Level.INFO); - } - log.addHandler(fileLogger); - } catch (Exception ex) { - System.out.println("NullJavaAI: Failed initializing the logger!"); - ex.printStackTrace(); - ret = -2; - } - - try { - int teamId = clb.SkirmishAI_getTeamId(); - log.info("initializing team " + teamId + ", id " + skirmishAIId); - - int numInfo = callback.SkirmishAI_Info_getSize(); - log.info("info (items: " + numInfo + ") ..."); - for (int i = 0; i < numInfo; i++) { - String key = callback.SkirmishAI_Info_getKey(i); - String value = callback.SkirmishAI_Info_getValue(i); - log.info(key + " = " + value); - } - - int numOptions = callback.SkirmishAI_OptionValues_getSize(); - log.info("options (items: " + numOptions + ") ..."); - for (int i = 0; i < numOptions; i++) { - String key = callback.SkirmishAI_OptionValues_getKey(i); - String value = callback.SkirmishAI_OptionValues_getValue(i); - log.info(key + " = " + value); - } - - ret = 0; - } catch (Exception ex) { - log.log(Level.SEVERE, "Failed initializing", ex); - log.log(Level.SEVERE, "msg: " + ex.getMessage()); - StackTraceElement[] stackTrace = ex.getStackTrace(); - for (int i = 0; i < stackTrace.length; i++) { - log.log(Level.SEVERE, "ste: " + stackTrace[i].toString()); - } - ret = -3; - } - - { - int numOptions = clb.SkirmishAI_OptionValues_getSize(); - log.info("sizeOptions: " + numOptions); - log.info("options:"); -// Pointer[] pKeys = evt.optionKeys.getPointerArray(0L, evt.sizeOptions); -// Pointer[] pValues = evt.optionValues.getPointerArray(0L, evt.sizeOptions); -// Properties options = new Properties(); -// for (int i = 0; i < evt.sizeOptions; i++) { -// options.setProperty(pKeys[i].getString(0L), pValues[i].getString(0L)); -// } - for (int i = 0; i < numOptions; i++) { - String key = clb.SkirmishAI_OptionValues_getKey(i); - String value = clb.SkirmishAI_OptionValues_getValue(i); - log.info(key + " = " + value); - } - log.info("stored"); - } - - return ret; - } - - @Override - public int update(int frame) { - - try { - log.finer("update event ..."); - log.finer("frame: " + frame); - } catch (Exception ex) { - log.log(Level.WARNING, "Failed handling event", ex); - return -1; - } - - return 0; - } - - @Override - public int release(int reason) { - - int ret = -1; - - try { - int teamId = clb.SkirmishAI_getTeamId(); - log.info("releasing team " + teamId + ", id " + skirmishAIId); - - ret = 0; - } catch (Exception ex) { - log.log(Level.WARNING, "Failed releasing", ex); - ret = -2; - } - - return ret; - } -} diff --git a/AI/Skirmish/NullOOJavaAI/CMakeLists.txt b/AI/Skirmish/NullOOJavaAI/CMakeLists.txt deleted file mode 100644 index 195bb8f6313..00000000000 --- a/AI/Skirmish/NullOOJavaAI/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -### Generic Java Skirmish AI config -# - -configure_java_skirmish_ai("JavaOO") diff --git a/AI/Skirmish/NullOOJavaAI/VERSION b/AI/Skirmish/NullOOJavaAI/VERSION deleted file mode 100644 index ceab6e11ece..00000000000 --- a/AI/Skirmish/NullOOJavaAI/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.1 \ No newline at end of file diff --git a/AI/Skirmish/NullOOJavaAI/bin/ant.dependent.properties b/AI/Skirmish/NullOOJavaAI/bin/ant.dependent.properties deleted file mode 100644 index 71674185fe3..00000000000 --- a/AI/Skirmish/NullOOJavaAI/bin/ant.dependent.properties +++ /dev/null @@ -1,40 +0,0 @@ -# properties file for spring Java Skirmish AIs -# This is used when you do not use the spring repository for compiling. -# -# Paths are relative to the project home (which is ../ from this file). -# All values are optional, and listed here with their defaults. -# -# The default assumes that you have the spring source, -# and do a spring in-source build as it is the default with CMake and SCons, -# and as the buildbot does it. -# -# build-dir: ${spring.home}/AI/Skirmish/${ai.name}/ -# install-dir: ${spring.home}/dist/AI/Skirmish/${ai.name}/${ai.version}/ -# -# This file is loaded from within build.xml, -# but only if "Java-AIInterface/" and "JavaOO-AIWrapper/" dirs are not present. -# - -;spring.home=../../.. - -# This is used only in the next property -;build.home=${spring.home} -# Where jar files will be built -;build.dir=${build.home}/AI/Skirmish/${skirmish.ai.name} - -# This is used only in the next property -;dist.home=${spring.home}/dist -# Where jar files will be installed to -;dist.dir=${dist.home}/AI/Skirmish/${skirmish.ai.name}/${skirmish.ai.version} -# Where HTML JavaDoc files will be generated -;doc.dir=${dist.home}/AI/Skirmish/${skirmish.ai.name}/${skirmish.ai.version}/doc/jdoc - -# The following four are needed for compiling (to create the classpath) -# * Here we look for ./data/jlib/*.jar recursively -;ai.interface.src.home=${spring.home}/AI/Interfaces/Java -# * Here we look for AIInterface.jar -;ai.interface.build.home=${build.home}/AI/Interfaces/Java -# * Here we look for ./jlib/*.jar recursively -;ai.wrapper.oo.src.home=${spring.home}/AI/Wrappers/JavaOO -# * Here we look for JavaOO-AIWrapper.jar -;ai.wrapper.oo.build.home=${build.home}/AI/Wrappers/JavaOO diff --git a/AI/Skirmish/NullOOJavaAI/bin/ant.independent.properties b/AI/Skirmish/NullOOJavaAI/bin/ant.independent.properties deleted file mode 100644 index 4f5648f2935..00000000000 --- a/AI/Skirmish/NullOOJavaAI/bin/ant.independent.properties +++ /dev/null @@ -1,30 +0,0 @@ -# properties file for spring Java Skirmish AIs -# This is used when you do not use the spring repository for compiling. -# -# Paths are relative to the project home (which is ../ from this file). -# All values are optional, and listed here with their defaults. -# -# For the project, you do not need the spring sources, -# but you will need the "Java-AIInterface/" and "JavaOO-AIWrapper/" dirs -# instead, which contain the Java AI Interfaces jlibs plus its binary -# and source jar, and the Java OO Wrappers equal counterparts. -# -# build-dir: build/ -# install-dir: dist/ -# -# This file is loaded from within build.xml, -# but only if "Java-AIInterface/" and "JavaOO-AIWrapper/" dirs are present. -# - -# This is used only in the next property -;build.home=build -# Where jar files will be built -;build.dir=${build.home} - -# This is used only in the next property. -# You will want to set dist.home to your spring install dir. -;dist.home=dist -# Where jar files will be installed to -;dist.dir=${dist.home}/AI/Skirmish/${skirmish.ai.name}/${skirmish.ai.version} -# Where HTML JavaDoc files will be generated -;doc.dir=${dist.home}/AI/Skirmish/${skirmish.ai.name}/${skirmish.ai.version}/doc/jdoc diff --git a/AI/Skirmish/NullOOJavaAI/bin/build.xml b/AI/Skirmish/NullOOJavaAI/bin/build.xml deleted file mode 100644 index 32b0b4783eb..00000000000 --- a/AI/Skirmish/NullOOJavaAI/bin/build.xml +++ /dev/null @@ -1,342 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - . -File not found: ${ai.interface.build.home}/AIInterface.jar -Please make sure to compile the Java AI Interface -before trying to compile this AI, or request -an independent version of this project from -wherever you got this AI. - - - - . -File not found: ${ai.wrapper.oo.build.home}/JavaOO-AIWrapper.jar -Please make sure to compile the Java OO AI Wrapper -before trying to compile this AI, or request -an independent version of this project from -wherever you got this AI from. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Successfully bumped ${skirmish.ai.name} version from ${skirmish.ai.version} to ${skirmish.ai.version.new}. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - . -Please specify either home.new or name.new, for example: - ant create-independent -Dhome.new=~/projects/SpringSkirmishAIs/ -or - ant create-independent -Dname.new=MyJavaAI - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Spring source independant Java Skirmish AI project copied to: - ${project.dir.new} - -You may want to change your java source package structure -and the data/AIInto.lua file for the new project. - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AI/Skirmish/NullOOJavaAI/data/AIInfo.lua b/AI/Skirmish/NullOOJavaAI/data/AIInfo.lua deleted file mode 100644 index 91420c5e704..00000000000 --- a/AI/Skirmish/NullOOJavaAI/data/AIInfo.lua +++ /dev/null @@ -1,53 +0,0 @@ --- --- Info Definition Table format --- --- --- These keywords must be lowercase for LuaParser to read them. --- --- key: user defined or one of the SKIRMISH_AI_PROPERTY_* defines in --- SSkirmishAILibrary.h --- value: the value of the property --- desc: the description (could be used as a tooltip) --- --- --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -local infos = { - { - key = 'shortName', - value = 'NullOOJavaAI', - desc = 'machine conform name.', - }, - { - key = 'version', - value = '0.1', -- AI version - !This comment is used for parsing! - }, - { - key = 'className', - value = 'nulloojavaai.NullOOJavaAI', - desc = 'fully qualified name of a class that implements interface com.springrts.ai.AI', - }, - { - key = 'name', - value = 'high-level Java stub Skirmish AI', - desc = 'human readable name.', - }, - { - key = 'loadSupported', - value = 'no', - desc = 'whether this AI supports loading or not', - }, - { - key = 'interfaceShortName', - value = 'Java', -- AI Interface name - !This comment is used for parsing! - desc = 'the shortName of the AI interface this AI needs', - }, - { - key = 'interfaceVersion', - value = '0.1', -- AI Interface version - !This comment is used for parsing! - desc = 'the minimum version of the AI interface required by this AI', - }, -} - -return infos diff --git a/AI/Skirmish/NullOOJavaAI/data/AIOptions.lua b/AI/Skirmish/NullOOJavaAI/data/AIOptions.lua deleted file mode 100644 index d435d62e964..00000000000 --- a/AI/Skirmish/NullOOJavaAI/data/AIOptions.lua +++ /dev/null @@ -1,15 +0,0 @@ --- --- Custom Options Definition Table format --- --- A detailed example of how this format works can be found --- in the spring source under: --- AI/Skirmish/NullAI/data/AIOptions.lua --- --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - -local options = { -} - -return options - diff --git a/AI/Skirmish/NullOOJavaAI/manifest.mf b/AI/Skirmish/NullOOJavaAI/manifest.mf deleted file mode 100755 index 59499bce4a2..00000000000 --- a/AI/Skirmish/NullOOJavaAI/manifest.mf +++ /dev/null @@ -1,2 +0,0 @@ -Manifest-Version: 1.0 - diff --git a/AI/Skirmish/NullOOJavaAI/src/main/java/nulloojavaai/NullOOJavaAI.java b/AI/Skirmish/NullOOJavaAI/src/main/java/nulloojavaai/NullOOJavaAI.java deleted file mode 100644 index 9ffabe1eae6..00000000000 --- a/AI/Skirmish/NullOOJavaAI/src/main/java/nulloojavaai/NullOOJavaAI.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - Copyright (c) 2008 Robin Vobruba - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package nulloojavaai; - - -import com.springrts.ai.AI; -import com.springrts.ai.oo.AIFloat3; -import com.springrts.ai.oo.OOAI; -import com.springrts.ai.oo.clb.*; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Properties; -import java.util.List; -import java.util.logging.*; - -/** - * Serves as Interface for a Java Skirmish AIs for the Spring engine. - * - * @author hoijui - */ -public class NullOOJavaAI extends OOAI implements AI { - - private static class MyCustomLogFormatter extends Formatter { - - private DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss:SSS dd.MM.yyyy"); - - public String format(LogRecord record) { - - // Create a StringBuffer to contain the formatted record - // start with the date. - StringBuffer sb = new StringBuffer(); - - // Get the date from the LogRecord and add it to the buffer - Date date = new Date(record.getMillis()); - sb.append(dateFormat.format(date)); - sb.append(" "); - - // Get the level name and add it to the buffer - sb.append(record.getLevel().getName()); - sb.append(": "); - - // Get the formatted message (includes localization - // and substitution of parameters) and add it to the buffer - sb.append(formatMessage(record)); - sb.append("\n"); - - return sb.toString(); - } - } - - private static void logProperties(Logger log, Level level, Properties props) { - - log.log(level, "properties (items: " + props.size() + "):"); - for (String key : props.stringPropertyNames()) { - log.log(level, key + " = " + props.getProperty(key)); - } - } - - private int skirmishAIId = -1; - private int teamId = -1; - private Properties info = null; - private Properties optionValues = null; - private OOAICallback clb = null; - private String myLogFile = null; - private Logger log = null; - - private static final int DEFAULT_ZONE = 0; - - - public NullOOJavaAI() {} - - private int sendTextMsg(String msg) { - - try { - clb.getGame().sendTextMessage("/say " + msg, DEFAULT_ZONE); - } catch (Exception ex) { - ex.printStackTrace(); - return 1; - } - - return 0; - } - public boolean isDebugging() { - return true; - } - - @Override - public int init(int skirmishAIId, OOAICallback callback) { - - int ret = -1; - - this.skirmishAIId = skirmishAIId; - this.clb = callback; - - this.teamId = clb.getSkirmishAI().getTeamId(); - - info = new Properties(); - Info inf = clb.getSkirmishAI().getInfo(); - int numInfo = inf.getSize(); - for (int i = 0; i < numInfo; i++) { - String key = inf.getKey(i); - String value = inf.getValue(i); - info.setProperty(key, value); - } - - optionValues = new Properties(); - OptionValues opVals = clb.getSkirmishAI().getOptionValues(); - int numOpVals = opVals.getSize(); - for (int i = 0; i < numOpVals; i++) { - String key = opVals.getKey(i); - String value = opVals.getValue(i); - optionValues.setProperty(key, value); - } - - // initialize the log - try { - myLogFile = callback.getDataDirs().locatePath("log-team-" + teamId + ".txt", true, true, false, false); - FileHandler fileLogger = new FileHandler(myLogFile, false); - fileLogger.setFormatter(new MyCustomLogFormatter()); - fileLogger.setLevel(Level.ALL); - log = Logger.getLogger("nulloojavaai"); - log.addHandler(fileLogger); - if (isDebugging()) { - log.setLevel(Level.ALL); - } else { - log.setLevel(Level.INFO); - } - } catch (Exception ex) { - System.out.println("NullOOJavaAI: Failed initializing the logger!"); - ex.printStackTrace(); - ret = -2; - } - - try { - log.info("initializing team " + teamId); - - log.log(Level.FINE, "info:"); - logProperties(log, Level.FINE, info); - - log.log(Level.FINE, "options:"); - logProperties(log, Level.FINE, optionValues); - - ret = 0; - } catch (Exception ex) { - log.log(Level.SEVERE, "Failed initializing", ex); - ret = -3; - } - - return ret; - } - - @Override - public int release(int reason) { - return 0; // signaling: OK - } - - @Override - public int update(int frame) { - - if (frame % 300 == 0) { - sendTextMsg("listing resources ..."); - List resources = clb.getResources(); - for (Resource resource : resources) { - sendTextMsg("Resource " + resource.getName() + " optimum: " - + resource.getOptimum()); - sendTextMsg("Resource " + resource.getName() + " current: " - + clb.getEconomy().getCurrent(resource)); - sendTextMsg("Resource " + resource.getName() + " income: " - + clb.getEconomy().getIncome(resource)); - sendTextMsg("Resource " + resource.getName() + " storage: " - + clb.getEconomy().getStorage(resource)); - sendTextMsg("Resource " + resource.getName() + " usage: " - + clb.getEconomy().getUsage(resource)); - } - } - - return 0; // signaling: OK - } - - @Override - public int message(int player, String message) { - return 0; // signaling: OK - } - - @Override - public int unitCreated(Unit unit, Unit builder) { - - int ret = sendTextMsg("unitCreated: " + unit.toString()); - sendTextMsg("unitCreated def: " + unit.getDef().getName()); - List buildOptions = unit.getDef().getBuildOptions(); - for (UnitDef unitDef : buildOptions) { - sendTextMsg("\tbuildOption x: " + unitDef.getName() + "\t" + unitDef.getHumanName() + "\t" + unitDef.toString() + "\t" + unitDef.hashCode()); - } - - return ret; - } - - @Override - public int unitFinished(Unit unit) { - return 0; // signaling: OK - } - - @Override - public int unitIdle(Unit unit) { - return 0; // signaling: OK - } - - @Override - public int unitMoveFailed(Unit unit) { - return 0; // signaling: OK - } - - @Override - public int unitDamaged(Unit unit, Unit attacker, float damage, AIFloat3 dir, WeaponDef weaponDef, boolean paralyzed) { - return 0; // signaling: OK - } - - @Override - public int unitDestroyed(Unit unit, Unit attacker) { - return 0; // signaling: OK - } - - @Override - public int unitGiven(Unit unit, int oldTeamId, int newTeamId) { - return 0; // signaling: OK - } - - @Override - public int unitCaptured(Unit unit, int oldTeamId, int newTeamId) { - return 0; // signaling: OK - } - - @Override - public int enemyEnterLOS(Unit enemy) { - return 0; // signaling: OK - } - - @Override - public int enemyLeaveLOS(Unit enemy) { - return 0; // signaling: OK - } - - @Override - public int enemyEnterRadar(Unit enemy) { - return 0; // signaling: OK - } - - @Override - public int enemyLeaveRadar(Unit enemy) { - return 0; // signaling: OK - } - - @Override - public int enemyDamaged(Unit enemy, Unit attacker, float damage, AIFloat3 dir, WeaponDef weaponDef, boolean paralyzed) { - return 0; // signaling: OK - } - - @Override - public int enemyDestroyed(Unit enemy, Unit attacker) { - return 0; // signaling: OK - } - - @Override - public int weaponFired(Unit unit, WeaponDef weaponDef) { - return 0; // signaling: OK - } - - @Override - public int playerCommand(java.util.List units, int commandTopicId, int playerId) { - return 0; // signaling: OK - } - - @Override - public int commandFinished(Unit unit, int commandId, int commandTopicId) { - return 0; // signaling: OK - } - - @Override - public int seismicPing(AIFloat3 pos, float strength) { - return 0; // signaling: OK - } - - @Override - public int load(String file) { - return 0; // signaling: OK - } - - @Override - public int save(String file) { - return 0; // signaling: OK - } - - @Override - public int enemyCreated(Unit enemy) { - return 0; // signaling: OK - } - - @Override - public int enemyFinished(Unit enemy) { - return 0; // signaling: OK - } - -} diff --git a/AI/Wrappers/JavaOO/CMakeLists.txt b/AI/Wrappers/JavaOO/CMakeLists.txt deleted file mode 100644 index da45a64c6e6..00000000000 --- a/AI/Wrappers/JavaOO/CMakeLists.txt +++ /dev/null @@ -1,379 +0,0 @@ -### Java OO AI Wrapper -# -# Global variables set in this file: -# * BUILD_JavaOO_AIWRAPPER -# - -#enable_language(Java) - -# includes rts/build/cmake/UtilJava.cmake -include(UtilJava) - - -set(myName "JavaOO") - - -# Check if the user wants to compile the wrapper -if ("${AI_TYPES}" STREQUAL "ALL" OR "${AI_TYPES}" STREQUAL "JAVA") - set(AIWRAPPERS_JAVA TRUE) -else ("${AI_TYPES}" STREQUAL "ALL" OR "${AI_TYPES}" STREQUAL "JAVA") - set(AIWRAPPERS_JAVA FALSE) -endif ("${AI_TYPES}" STREQUAL "ALL" OR "${AI_TYPES}" STREQUAL "JAVA") - - -# Check dependencies of the wrapper are met -if (AIWRAPPERS_JAVA AND BUILD_Java_AIINTERFACE AND AWK_FOUND AND NOT myName MATCHES "${AI_EXCLUDE_REGEX}") - set_global(BUILD_${myName}_AIWRAPPER TRUE) -else (AIWRAPPERS_JAVA AND BUILD_Java_AIINTERFACE AND AWK_FOUND AND NOT myName MATCHES "${AI_EXCLUDE_REGEX}") - set_global(BUILD_${myName}_AIWRAPPER FALSE) - message("warning: Java OO AI Wrapper will not be built!") -endif (AIWRAPPERS_JAVA AND BUILD_Java_AIINTERFACE AND AWK_FOUND AND NOT myName MATCHES "${AI_EXCLUDE_REGEX}") - - -# Build -if (BUILD_${myName}_AIWRAPPER) - set(myDir "${CMAKE_CURRENT_SOURCE_DIR}") - get_last_path_part(dirName ${myDir}) - set(myName "${dirName}") - set(myTarget "${myName}-AIWrapper") - set(myGenTarget "${myTarget}-generateSources") - set(myJavaTarget "${myTarget}-java") - set(myPomTarget "${myTarget}-pom") - set(mySourceDirRel "src/main/java") - make_absolute(mySourceDir "${myDir}" "${mySourceDirRel}") - - ai_wrapper_message(STATUS "Found AI Wrapper: ${myName}") - - - # Build library - set(myPkgFirstPart "com") - set(myParentPkg "${myPkgFirstPart}/springrts/ai") - set(myPkg "${myParentPkg}/oo") - set(myBinDir "${myDir}/bin") - set(commonAwkScriptsDir "${CMAKE_SOURCE_DIR}/AI/Wrappers/CUtils/bin") - set(myBuildDir "${CMAKE_CURRENT_BINARY_DIR}") - set(myGeneratedSourceDir "${myBuildDir}/src-generated/main/java") - set(myJavaBuildDir "${myBuildDir}/classes") - set(myJarFile "${myName}-AIWrapper") - set(myBinJarFile "${myJarFile}.jar") - set(mySrcJarFile "${myJarFile}-src.jar") - set(myJLibDir "${myDir}/jlib") - find_java_lib(vecmath_jar "vecmath" "${myJLibDir}") - set(myJLibs "${vecmath_jar}") - set(jAiIntJavaSourceDir "${JAVA_SRC_DIR_Java_AIINTERFACE}") - set(jAiIntJavaGeneratedSourceDir "${JAVA_GEN_SRC_DIR_Java_AIINTERFACE}") - set(myClassPath "${myJLibs}${PATH_DELIM_H}${CLASSPATH_Java_AIINTERFACE}") - - set_global(${myName}_AIWRAPPER_JAR_BIN "${myBuildDir}/${myBinJarFile}") - set_global(${myName}_AIWRAPPER_JAR_SRC "${myBuildDir}/${mySrcJarFile}") - set_global(${myName}_AIWRAPPER_JAR_CLASSPATH "${myJLibs}${PATH_DELIM_H}${${myName}_AIWRAPPER_JAR_BIN}") - set_global(${myName}_AIWRAPPER_TARGET "${myTarget}") - set_global(SOURCE_ROOT_${myName}_AIWRAPPER "${myDir}") - set_global(BUILD_ROOT_${myName}_AIWRAPPER "${CMAKE_CURRENT_BINARY_DIR}") - - # Locate the manifest file - find_manifest_file("${myDir}" myManifestFile) - if (myManifestFile) - set(myBinJarArgs "cmf" "${myManifestFile}") - else (myManifestFile) - set(myBinJarArgs "cf") - endif (myManifestFile) - - # remove all generated sources from build dir, if it exists - # (required for build dirs of git:master from before 21. September 2010) - file(REMOVE_RECURSE "${myGeneratedSourceDir}/${myPkg}") - - - # Generate sources - # ---------------- - - set(commonAwkScriptArgs - "-v" "INTERFACE_SOURCE_DIR=${jAiIntJavaSourceDir}" - "-v" "INTERFACE_GENERATED_SOURCE_DIR=${jAiIntJavaGeneratedSourceDir}" - "-v" "JAVA_GENERATED_SOURCE_DIR=${myGeneratedSourceDir}" - "-f" "${commonAwkScriptsDir}/common.awk" - "-f" "${commonAwkScriptsDir}/commonDoc.awk" - ) - - set(mySources - "${mySourceDir}/${myPkg}/AIEvent.java" - "${mySourceDir}/${myPkg}/AIFloat3.java" - "${mySourceDir}/${myPkg}/AIException.java" - "${mySourceDir}/${myPkg}/CallbackAIException.java" - "${mySourceDir}/${myPkg}/EventAIException.java" - ) - - set(myGenClbClasses - "Cheats" - "CommandDescription" - "Command" - "Damage" - "DataDirs" - "Debug" - "Drawer" - "Economy" - "Engine" - "FeatureDef" - "Feature" - "Figure" - "FlankingBonus" - "Game" - "GraphDrawer" - "GraphLine" - "Group" - "Info" - "Line" - "Log" - "Lua" - "Map" - "Mod" - "MoveData" - "OOAICallback" - "OptionValues" - "OrderPreview" - "Pathing" - "PathDrawer" - "Point" - "Resource" - "Roots" - "Shield" - "SkirmishAI" - "Team" - "OverlayTexture" - "UnitDef" - "Unit" - "Version" - "WeaponDef" - "WeaponMount" - "Weapon" - ) - set(myGeneratedCallbackSources ) - foreach (className ${myGenClbClasses}) - list(APPEND myGeneratedCallbackSources - "${myGeneratedSourceDir}/${myPkg}/clb/${className}.java" - "${myGeneratedSourceDir}/${myPkg}/clb/Abstract${className}.java" - "${myGeneratedSourceDir}/${myPkg}/clb/Stub${className}.java" - ) - if ("${className}" STREQUAL "CommandDescription") - list(APPEND myGeneratedCallbackSources - "${myGeneratedSourceDir}/${myPkg}/clb/WrappGroupSupportedCommand.java" - "${myGeneratedSourceDir}/${myPkg}/clb/WrappUnitSupportedCommand.java" - ) - elseif ("${className}" STREQUAL "Command") - list(APPEND myGeneratedCallbackSources - "${myGeneratedSourceDir}/${myPkg}/clb/WrappCurrentCommand.java" - ) - else () - list(APPEND myGeneratedCallbackSources - "${myGeneratedSourceDir}/${myPkg}/clb/Wrapp${className}.java" - ) - endif () - endforeach (className) - - set(myGeneratedEventSources - "${myGeneratedSourceDir}/${myPkg}/AbstractOOAI.java" - "${myGeneratedSourceDir}/${myPkg}/IOOAI.java" - "${myGeneratedSourceDir}/${myPkg}/OOAI.java" - "${myGeneratedSourceDir}/${myPkg}/IOOEventAI.java" - "${myGeneratedSourceDir}/${myPkg}/OOEventAI.java" - "${myGeneratedSourceDir}/${myPkg}/evt/UnitAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/UnitLifeStateAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/UnitTeamChangeAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/EnemyAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/LoadSaveAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/CommandFinishedAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/EnemyCreatedAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/EnemyDamagedAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/EnemyDestroyedAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/EnemyFinishedAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/EnemyEnterLOSAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/EnemyEnterRadarAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/EnemyLeaveLOSAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/EnemyLeaveRadarAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/InitAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/LoadAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/MessageAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/LuaMessageAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/PlayerCommandAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/ReleaseAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/SaveAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/SeismicPingAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/UnitCapturedAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/UnitCreatedAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/UnitDamagedAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/UnitDestroyedAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/UnitFinishedAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/UnitGivenAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/UnitIdleAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/UnitMoveFailedAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/UpdateAIEvent.java" - "${myGeneratedSourceDir}/${myPkg}/evt/WeaponFiredAIEvent.java" - ) - - set(myGeneratedSources - ${myGeneratedCallbackSources} - ${myGeneratedEventSources} - ) - set_source_files_properties(${myGeneratedSources} PROPERTIES GENERATED TRUE) - - set(jAiIntJavaGeneratedSources - "${jAiIntJavaGeneratedSourceDir}/${myParentPkg}/AI.java" - "${jAiIntJavaGeneratedSourceDir}/${myParentPkg}/AICallback.java" - ) - set_source_files_properties(${jAiIntJavaGeneratedSources} PROPERTIES GENERATED TRUE) - - - # Assemble project generated targets (and their libraries) we depend on - set(myDependTargets "${Java_AIINTERFACE_TARGET}") - set(myDependLibFiles "${Java_AIINTERFACE_JAR_BIN}") - set_source_files_properties(${myDependLibFiles} PROPERTIES GENERATED TRUE) - - add_custom_target(${myJavaTarget} - DEPENDS - "${myBuildDir}/${myBinJarFile}" - "${myBuildDir}/${mySrcJarFile}") - add_dependencies(${myJavaTarget} ${myDependTargets}) - - - # generate sources: callback - add_custom_command( - OUTPUT - ${myGeneratedCallbackSources} - COMMAND "${CMAKE_COMMAND}" - "-E" "remove_directory" "${myGeneratedSourceDir}/${myPkg}/clb" - COMMAND "${CMAKE_COMMAND}" - "-E" "make_directory" "${myGeneratedSourceDir}/${myPkg}/clb" - COMMAND "${AWK_BIN}" - ${commonAwkScriptArgs} - "-f" "${commonAwkScriptsDir}/commonOOCallback.awk" - "-f" "${myBinDir}/wrappCallback.awk" - "${jAiIntJavaGeneratedSourceDir}/${myParentPkg}/AICallback.java" - DEPENDS - "${commonAwkScriptsDir}/common.awk" - "${commonAwkScriptsDir}/commonDoc.awk" - "${commonAwkScriptsDir}/commonOOCallback.awk" - "${myBinDir}/wrappCallback.awk" - # Handled through a target level dependency, see below - #"${jAiIntJavaGeneratedSourceDir}/${myParentPkg}/AICallback.java" - WORKING_DIRECTORY - "${myBinDir}" - COMMENT - " ${myTarget}: Generating callback sources" VERBATIM - ) - - # generate sources: events - add_custom_command( - OUTPUT - ${myGeneratedEventSources} - COMMAND "${CMAKE_COMMAND}" - "-E" "remove_directory" "${myGeneratedSourceDir}/${myPkg}/evt" - COMMAND "${CMAKE_COMMAND}" - "-E" "make_directory" "${myGeneratedSourceDir}/${myPkg}/evt" - COMMAND "${AWK_BIN}" - ${commonAwkScriptArgs} - "-f" "${myBinDir}/wrappEvents.awk" - "${jAiIntJavaGeneratedSourceDir}/${myParentPkg}/AI.java" - DEPENDS - "${commonAwkScriptsDir}/common.awk" - "${commonAwkScriptsDir}/commonDoc.awk" - "${myBinDir}/wrappEvents.awk" - "${jAiIntJavaGeneratedSourceDir}/${myParentPkg}/AI.java" - WORKING_DIRECTORY - "${myBinDir}" - COMMENT - " ${myTarget}: Generating event sources" VERBATIM - ) - - add_custom_target(${myGenTarget} DEPENDS ${myGeneratedSources}) - add_dependencies(${myGenTarget} ${Java_AIINTERFACE_TARGET_GENERATE_SOURCES}) # for AICallback.java - add_dependencies(generateSources ${myGenTarget}) - - - # Build the jars - # -------------- - - # Write list of source files to an arg-file - set(mySrcArgFile "${CMAKE_CURRENT_BINARY_DIR}/sourceFiles.txt") - if (EXISTS "${mySrcArgFile}") - file(REMOVE "${mySrcArgFile}") - endif (EXISTS "${mySrcArgFile}") - foreach (srcFile ${mySources} ${myGeneratedSources}) - file(APPEND "${mySrcArgFile}" "\"${srcFile}\"\n") - endforeach (srcFile) - - # compile and pack library - add_custom_command( - OUTPUT - "${myBuildDir}/${myBinJarFile}" - COMMAND "${CMAKE_COMMAND}" - "-E" "remove_directory" "${myJavaBuildDir}" - COMMAND "${CMAKE_COMMAND}" - "-E" "make_directory" "${myJavaBuildDir}" - COMMAND "${Java_JAVAC_EXECUTABLE}" - "${JAVA_COMPILE_FLAG_CONDITIONAL}" - "-Xlint:deprecation" - "-cp" "${myClassPath}" - "-d" "${myJavaBuildDir}" - "@${mySrcArgFile}" - COMMAND "${Java_JAR_EXECUTABLE}" ARGS - ${myBinJarArgs} "${myBuildDir}/${myBinJarFile}" - "-C" "${myJavaBuildDir}" "${myPkgFirstPart}" - DEPENDS - ${myDependLibFiles} - ${mySources} - ${myGeneratedSources} - WORKING_DIRECTORY - "${myBinDir}" - COMMENT - " ${myTarget}: Compiling sources and packing library ${myBinJarFile}" VERBATIM - ) - - # pack sources - add_custom_command( - OUTPUT - "${myBuildDir}/${mySrcJarFile}" - COMMAND "${Java_JAR_EXECUTABLE}" ARGS - "cf" "${${myName}_AIWRAPPER_JAR_SRC}" - "-C" "${mySourceDir}" "${myPkgFirstPart}" - COMMAND "${Java_JAR_EXECUTABLE}" ARGS - "uf" "${${myName}_AIWRAPPER_JAR_SRC}" - "-C" "${myGeneratedSourceDir}" "${myPkgFirstPart}" - DEPENDS - ${myJavaSources} - ${myJavaGeneratedSources} - WORKING_DIRECTORY - "${myBuildDir}" - COMMENT - " ${myTarget}: Creating sources archive ${mySrcJarFile}" VERBATIM - ) - - # This sets the version in pom.xml - # as we have no separate version for the wrapper, - # we use the one from the interface - set(myMavenProperties "-Dmy.version=${Java_AIINTERFACE_VERS}") - add_custom_command( - OUTPUT - "${myBuildDir}/pom-generated.xml" - COMMAND "${CMAKE_COMMAND}" - "-Dfile.in=${myDir}/pom.xml" - "-Dfile.out=${myBuildDir}/pom-generated.xml" - ${myMavenProperties} - "-P" "${CMAKE_MODULES_SPRING}/ConfigureFile.cmake" - DEPENDS - "${myDir}/pom.xml" - WORKING_DIRECTORY - "${myDir}" - COMMENT - " ${myTarget}: Configure pom.xml" VERBATIM - ) - set_source_files_properties("${myBuildDir}/pom-generated.xml" PROPERTIES GENERATED TRUE) - - add_custom_target(${myPomTarget} - DEPENDS - "${myBuildDir}/pom-generated.xml") - - add_custom_target(${myTarget} ALL) - - add_dependencies(${myTarget} ${myJavaTarget}) - -endif (BUILD_${myName}_AIWRAPPER) diff --git a/AI/Wrappers/JavaOO/bin/ant.properties b/AI/Wrappers/JavaOO/bin/ant.properties deleted file mode 100644 index 0807d79b8a8..00000000000 --- a/AI/Wrappers/JavaOO/bin/ant.properties +++ /dev/null @@ -1,26 +0,0 @@ -# Paths are relative to the project home (which is ../ from this file). -# All values are optional. - -;spring.home=../../.. - -# specify Java-AIInterface dirs -;spring.ai.interface.src.home=${spring.home}/AI/Interfaces/Java -;spring.ai.interface.src.dir=${spring.ai.interface.src.home}/src/main/java -;spring.ai.interface.build.home=${build.home}/AI/Interfaces/Java -;spring.ai.interface.build.dir=${spring.ai.interface.build.home}/src-generated/main/java - -# This is used only in the next property -;build.home=${spring.home}/build -# Where jar files will be built -;build.dir=${build.home}/AI/Wrappers/${wrapper.name} - -# Where generated sources shall be created in -;src.generated=${build.dir}/src-generated/main -;src.generated.java=${src.generated}/java - -# This is used only in the next property -;dist.home=${spring.home}/dist -# Where jar files will be installed to -;dist.dir=${dist.home}/AI/Wrappers/${wrapper.name} -# Where HTML JavaDoc files will be generated -;doc.dir=${dist.home}/AI/Wrappers/${wrapper.name}/doc/jdoc diff --git a/AI/Wrappers/JavaOO/bin/build.xml b/AI/Wrappers/JavaOO/bin/build.xml deleted file mode 100644 index 71e836bbb7a..00000000000 --- a/AI/Wrappers/JavaOO/bin/build.xml +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AI/Wrappers/JavaOO/bin/wrappCallback.awk b/AI/Wrappers/JavaOO/bin/wrappCallback.awk deleted file mode 100755 index 7824eb30137..00000000000 --- a/AI/Wrappers/JavaOO/bin/wrappCallback.awk +++ /dev/null @@ -1,1241 +0,0 @@ -#!/usr/bin/awk -f -# -# This awk script creates Java classes in OO style to wrap the C style -# JNI based AI Callback wrapper. -# In other words, the output of this file wraps: -# com/springrts/ai/AICallback.java -# which wraps: -# rts/ExternalAI/Interface/SSkirmishAICallback.h -# and -# rts/ExternalAI/Interface/AISCommands.h -# -# This script uses functions from the following files: -# * common.awk -# * commonDoc.awk -# * commonOOCallback.awk -# Variables that can be set on the command-line (with -v): -# * GENERATED_SOURCE_DIR : the generated sources root dir -# * JAVA_GENERATED_SOURCE_DIR : the generated java sources root dir -# * INTERFACE_SOURCE_DIR : the Java AI Interfaces static source files root dir -# * INTERFACE_GENERATED_SOURCE_DIR : the Java AI Interfaces generated source files root dir -# -# usage: -# awk -f thisScript.awk -f common.awk -f commonDoc.awk -f commonOOCallback.awk -# awk -f thisScript.awk -f common.awk -f commonDoc.awk -f commonOOCallback.awk \ -# -v 'GENERATED_SOURCE_DIR=/tmp/build/AI/Interfaces/Java/src-generated' -# - -BEGIN { - # initialize things - - # define the field splitter(-regex) - FS = "(\\()|(\\);)"; - IGNORECASE = 0; - - # Used by other scripts - JAVA_MODE = 1; - - # These vars can be assigned externally, see file header. - # Set the default values if they were not supplied on the command line. - if (!GENERATED_SOURCE_DIR) { - GENERATED_SOURCE_DIR = "../src-generated/main"; - } - if (!JAVA_GENERATED_SOURCE_DIR) { - JAVA_GENERATED_SOURCE_DIR = GENERATED_SOURCE_DIR "/java"; - } - if (!INTERFACE_SOURCE_DIR) { - INTERFACE_SOURCE_DIR = "../../../Interfaces/Java/src/main/java"; - } - if (!INTERFACE_GENERATED_SOURCE_DIR) { - INTERFACE_GENERATED_SOURCE_DIR = "../../../Interfaces/Java/src-generated/main/java"; - } - - myMainPkgA = "com.springrts.ai"; - myParentPkgA = myMainPkgA ".oo"; - myPkgA = myParentPkgA ".clb"; - myPkgD = convertJavaNameFormAToD(myPkgA); - myClassVar = "ooClb"; - myWrapClass = "AICallback"; - myWrapVar = "innerCallback"; - - myBufferedClasses["UnitDef"] = 1; - myBufferedClasses["WeaponDef"] = 1; - myBufferedClasses["FeatureDef"] = 1; - - retParamName = "__retVal"; -} - -function createJavaFileName(clsName_c) { - return JAVA_GENERATED_SOURCE_DIR "/" myPkgD "/" clsName_c ".java"; -} - - -function printHeader(outFile_h, javaPkg_h, javaClassName_h, isInterface_h, - implementsInterface_h, isJniBound_h, isAbstract_h, implementsClass_h) { - - if (isInterface_h) { - classOrInterface_h = "interface"; - } else if (isAbstract_h) { - classOrInterface_h = "abstract class"; - } else { - classOrInterface_h = "class"; - } - - extensionsPart_h = ""; - if (isInterface_h) { - extensionsPart_h = " extends Comparable<" javaClassName_h ">"; - } else if (isAbstract_h) { - extensionsPart_h = " implements " implementsInterface_h ""; - } else { - extensionsPart_h = " extends " implementsClass_h " implements " implementsInterface_h; - } - - printCommentsHeader(outFile_h); - print("") >> outFile_h; - print("package " javaPkg_h ";") >> outFile_h; - print("") >> outFile_h; - print("") >> outFile_h; - print("import " myParentPkgA ".CallbackAIException;") >> outFile_h; - print("import " myParentPkgA ".AIFloat3;") >> outFile_h; - if (isJniBound_h) { - print("import " myMainPkgA ".AICallback;") >> outFile_h; - print("import " myMainPkgA ".Util;") >> outFile_h; - } - print("") >> outFile_h; - print("/**") >> outFile_h; - print(" * @author AWK wrapper script") >> outFile_h; - print(" * @version GENERATED") >> outFile_h; - print(" */") >> outFile_h; - print("public " classOrInterface_h " " javaClassName_h extensionsPart_h " {") >> outFile_h; - print("") >> outFile_h; -} - - -function getNullTypeValue(fRet_ntv) { - - if (fRet_ntv == "void") { - return ""; - } else if (fRet_ntv == "String") { - return "\"\""; - } else if (fRet_ntv == "AIFloat3") { - #return "new AIFloat3(0.0f, 0.0f, 0.0f)"; - return "null"; - } else if (fRet_ntv == "java.awt.Color") { - #return "java.awt.Color.BLACK"; - return "null"; - } else if (startsWithCapital(fRet_ntv)) { - # must be a class - return "null"; - } else if (match(fRet_ntv, /^java.util.List/)) { - return "null"; - } else if (match(fRet_ntv, /^java.util.Map/)) { - return "null"; - } else if (fRet_ntv == "boolean") { - return "false"; - } else { - return "0"; - } -} -function printTripleFunc(fRet_tr, fName_tr, fParams_tr, thrownExceptions_tr, outFile_int_tr, outFile_stb_tr, outFile_jni_tr, printIntAndStb_tr, noOverride_tr, isDeprecated_tr) { - - _funcHdr_tr = "public " fRet_tr " " fName_tr "(" fParams_tr ")"; - if (thrownExceptions_tr != "") { - _funcHdr_tr = _funcHdr_tr " throws " thrownExceptions_tr; - } - - if (printIntAndStb_tr) { - print("\t" _funcHdr_tr ";") >> outFile_int_tr; - print("") >> outFile_int_tr; - - isSimpleGetter_tr = ((fRet_tr != "void") && (fParams_tr == "") && match(fName_tr, /^(get|is)/)); - if ((fName_tr == "isEnabled") && match(outFile_int_tr, /Cheats\.java$/)) { - # an exception, because this has a setter anyway - isSimpleGetter_tr = 0; - } - nullTypeValue_tr = getNullTypeValue(fRet_tr); - if (isSimpleGetter_tr) { - # create an additional private member and setter - propName_tr = fName_tr; - sub(/^get/, "", propName_tr); - propName_tr = lowerize(propName_tr); - fSetterName_tr = fName_tr; - sub(/^(get|is)/, "set", fSetterName_tr); - print("\t" "public void " fSetterName_tr "(" fRet_tr " " propName_tr ")" " {") >> outFile_stb_tr; - print("\t\t" "this." propName_tr " = " propName_tr ";") >> outFile_stb_tr; - print("\t" "}") >> outFile_stb_tr; - print("\t" "private " fRet_tr " " propName_tr " = " nullTypeValue_tr ";") >> outFile_stb_tr; - if (isDeprecated_tr) { - # this prevents javac from outputting a warning - print("\t" "/** @deprecated */") >> outFile_stb_tr; - } - print("\t" "@Override") >> outFile_stb_tr; - print("\t" _funcHdr_tr " {") >> outFile_stb_tr; - print("\t\t" "return " propName_tr ";") >> outFile_stb_tr; - } else { - print("\t" "@Override") >> outFile_stb_tr; - print("\t" _funcHdr_tr " {") >> outFile_stb_tr; - # simply return a null like value - if (fRet_tr == "void") { - # return nothing - } else { - print("\t\t" "return " nullTypeValue_tr ";") >> outFile_stb_tr; - } - } - print("\t" "}") >> outFile_stb_tr; - print("") >> outFile_stb_tr; - } - - if (!noOverride_tr) { - print("\t" "@Override") >> outFile_jni_tr; - } - print("\t" _funcHdr_tr " {") >> outFile_jni_tr; - print("") >> outFile_jni_tr; -} - - -function printClasses() { - - # look for AVAILABLE indicators - for (_memberId in cls_memberId_metaComment) { - if (match(cls_memberId_metaComment[_memberId], /AVAILABLE:/)) { - _availCls = cls_memberId_metaComment[_memberId]; - sub(/^.*AVAILABLE:/, "", _availCls); - sub(/[ \t].*$/, "", _availCls); - clsAvailInd_memberId_cls[_memberId] = _availCls; - } - } - - c_size_cs = cls_id_name["*"]; - for (c=0; c < c_size_cs; c++) { - cls_cs = cls_id_name[c]; - anc_size_cs = cls_name_implIds[cls_cs ",*"]; - - printIntAndStb_cs = 1; - for (a=0; a < anc_size_cs; a++) { - implId_cs = cls_name_implIds[cls_cs "," a]; - printClass(implId_cs, cls_cs, printIntAndStb_cs); - # only print interface and stub when printing the first impl-class - printIntAndStb_cs = 0; - } - } -} - - -function printClass(implId_c, clsName_c, printIntAndStb_c) { - - implCls_c = implId_c; - sub(/^.*,/, "", implCls_c); - - clsName_int_c = clsName_c; - clsName_abs_c = "Abstract" clsName_int_c; - clsName_stb_c = "Stub" clsName_int_c; - _fullClsName = cls_implId_fullClsName[implId_c]; - if (_fullClsName != clsName_c) { - lastAncName_c = implId_c; - sub(/,[^,]*$/, "", lastAncName_c); # remove class name - sub(/^.*,/, "", lastAncName_c); # remove pre last ancestor name - noInterfaceIndices_c = lowerize(lastAncName_c) "Id"; - } else { - noInterfaceIndices_c = 0; - } - clsName_jni_c = "Wrapp" _fullClsName; - - if (printIntAndStb_c) { - outFile_int_c = createJavaFileName(clsName_int_c); - outFile_abs_c = createJavaFileName(clsName_abs_c); - outFile_stb_c = createJavaFileName(clsName_stb_c); - } - outFile_jni_c = createJavaFileName(clsName_jni_c); - - if (printIntAndStb_c) { - printHeader(outFile_int_c, myPkgA, clsName_int_c, 1, 0, 0, 0, 0); - printHeader(outFile_abs_c, myPkgA, clsName_abs_c, 0, clsName_int_c, 0, 1, 0); - printHeader(outFile_stb_c, myPkgA, clsName_stb_c, 0, clsName_int_c, 0, 0, clsName_abs_c); - } - printHeader( outFile_jni_c, myPkgA, clsName_jni_c, 0, clsName_int_c, 1, 0, clsName_abs_c); - - # prepare additional indices names - addInds_size_c = split(cls_implId_indicesArgs[implId_c], addInds_c, ","); - for (ai=1; ai <= addInds_size_c; ai++) { - sub(/int /, "", addInds_c[ai]); - addInds_c[ai] = trim(addInds_c[ai]); - } - - myInnerClb = myClassVar ".getInnerCallback()"; - - - # print private vars - print("\t" "private " myWrapClass " " myWrapVar " = null;") >> outFile_jni_c; - # print additionalVars - for (ai=1; ai <= addInds_size_c; ai++) { - print("\t" "private int " addInds_c[ai] " = -1;") >> outFile_jni_c; - } - print("") >> outFile_jni_c; - - - # print constructor - ctorParams = myWrapClass " " myWrapVar; - addIndPars_c = ""; - for (ai=1; ai <= addInds_size_c; ai++) { - addIndPars_c = addIndPars_c ", int " addInds_c[ai]; - } - ctorParams = ctorParams addIndPars_c; - ctorParamsNoTypes = removeParamTypes(ctorParams); - sub(/^, /, "", addIndPars_c); - addIndParsNoTypes_c = removeParamTypes(addIndPars_c); - condAddIndPars_c = (addIndPars_c == "") ? "" : ", "; - print("\t" "public " clsName_jni_c "(" ctorParams ") {") >> outFile_jni_c; - print("") >> outFile_jni_c; - print("\t\t" "this." myWrapVar " = " myWrapVar ";") >> outFile_jni_c; - # init additionalVars - for (ai=1; ai <= addInds_size_c; ai++) { - addIndName = addInds_c[ai]; - print("\t\t" "this." addIndName " = " addIndName ";") >> outFile_jni_c; - } - print("\t" "}") >> outFile_jni_c; - print("") >> outFile_jni_c; - - - # print additional vars fetchers - for (ai=1; ai <= addInds_size_c; ai++) { - addIndName = addInds_c[ai]; - addIndNameCap = capitalize(addIndName); - - printIntAndStb_tmp_c = printIntAndStb_c; - _noOverride = 0; - if ((noInterfaceIndices_c != 0) && (addIndName == noInterfaceIndices_c)) { - printIntAndStb_tmp_c = 0; - _noOverride = 1; - } - - # Print the direct fetcher function, eg. "int getUnitId()" - _fRet = "int"; - _fName = "get" addIndNameCap; - _fParams = ""; - _fExceps = ""; - _fIsDeprecated = 0; - printTripleFunc(_fRet, _fName, _fParams, _fExceps, outFile_int_c, outFile_stb_c, outFile_jni_c, printIntAndStb_tmp_c, _noOverride, _fIsDeprecated); - print("\t\t" "return " addIndName ";") >> outFile_jni_c; - print("\t" "}") >> outFile_jni_c; - print("") >> outFile_jni_c; - - # Print the OO entity fetcher function if applicable, eg. "Unit getUnit()" - addIndNameCapOO = addIndNameCap; - _hadId = sub(/Id$/, "", addIndNameCapOO); - if (_hadId && (addIndNameCapOO in cls_name_id) && (addIndNameCapOO != clsName_int_c)) { - _refObj = addIndNameCapOO; # example: Unit - _implId = implId_m "," _refObj; - if (_implId in cls_implId_fullClsName) { - _fullClsName = cls_implId_fullClsName[_implId]; - } else if (cls_name_implIds[_refObj ",*"] == 1) { - _fullClsName = cls_name_implIds[_refObj ",0"]; - _fullClsName = cls_implId_fullClsName[_fullClsName]; - } else { - print("ERROR: failed finding the full class name for: " _refObj); - exit(1); - } - - _wrappGetInst_params = myWrapVar; - for (aij=1; aij <= ai; aij++) { - _wrappGetInst_params = _wrappGetInst_params ", " addInds_c[aij]; - } - - _fRet = addIndNameCapOO; - _fName = "get" addIndNameCapOO; - _fParams = ""; - _fExceps = ""; - _fIsDeprecated = 0; - printTripleFunc(_fRet, _fName, _fParams, _fExceps, outFile_int_c, outFile_stb_c, outFile_jni_c, printIntAndStb_tmp_c, _noOverride, _fIsDeprecated); - print("\t\t" "return Wrapp" _fullClsName ".getInstance(" _wrappGetInst_params ");") >> outFile_jni_c; - print("\t" "}") >> outFile_jni_c; - print("") >> outFile_jni_c; - } - } - - # print static instance fetcher method - { - clsIsBuffered_c = isBufferedClass(clsName_c); - _isAvailableMethod = ""; - for (_memId in clsAvailInd_memberId_cls) { - if (clsAvailInd_memberId_cls[_memId] == clsName_c) { - _isAvailableMethod = _memId; - gsub(/,/, "_", _isAvailableMethod); - } - } - - if (clsIsBuffered_c) { - print("\t" "private static java.util.Map _buffer_instances = new java.util.HashMap();") >> outFile_jni_c; - print("") >> outFile_jni_c; - } - print("\t" "public static " clsName_c " getInstance(" ctorParams ") {") >> outFile_jni_c; - print("") >> outFile_jni_c; - lastParamName = ctorParamsNoTypes; - sub(/^.*,[ \t]*/, "", lastParamName); - if (match(lastParamName, /^[^ \t]+Id$/)) { - # id's < 0 are invalid, return null - print("\t\t" "if (" lastParamName " < 0) {") >> outFile_jni_c; - print("\t\t\t" "return null;") >> outFile_jni_c; - print("\t\t" "}") >> outFile_jni_c; - print("") >> outFile_jni_c; - } - print("\t\t" clsName_c " _ret = null;") >> outFile_jni_c; - if (_isAvailableMethod == "") { - print("\t\t" "_ret = new " clsName_jni_c "(" ctorParamsNoTypes ");") >> outFile_jni_c; - } else { - print("\t\t" "boolean isAvailable = " myWrapVar "." _isAvailableMethod "(" addIndParsNoTypes_c ");") >> outFile_jni_c; - print("\t\t" "if (isAvailable) {") >> outFile_jni_c; - print("\t\t\t" "_ret = new " clsName_jni_c "(" ctorParamsNoTypes ");") >> outFile_jni_c; - print("\t\t" "}") >> outFile_jni_c; - } - if (clsIsBuffered_c) { - if (_isAvailableMethod == "") { - print("\t\t" "{") >> outFile_jni_c; - } else { - print("\t\t" "if (_ret != null) {") >> outFile_jni_c; - } - print("\t\t\t" "Integer indexHash = _ret.hashCode();") >> outFile_jni_c; - print("\t\t\t" "if (_buffer_instances.containsKey(indexHash)) {") >> outFile_jni_c; - print("\t\t\t\t" "_ret = _buffer_instances.get(indexHash);") >> outFile_jni_c; - print("\t\t\t" "} else {") >> outFile_jni_c; - print("\t\t\t\t" "_buffer_instances.put(indexHash, _ret);") >> outFile_jni_c; - print("\t\t\t" "}") >> outFile_jni_c; - print("\t\t" "}") >> outFile_jni_c; - } - print("\t\t" "return _ret;") >> outFile_jni_c; - print("\t" "}") >> outFile_jni_c; - print("") >> outFile_jni_c; - } - - - if (printIntAndStb_c) { - # print compareTo(other) method - { - print("\t" "@Override") >> outFile_abs_c; - print("\t" "public int compareTo(" clsName_c " other) {") >> outFile_abs_c; - print("\t\t" "final int BEFORE = -1;") >> outFile_abs_c; - print("\t\t" "final int EQUAL = 0;") >> outFile_abs_c; - print("\t\t" "final int AFTER = 1;") >> outFile_abs_c; - print("") >> outFile_abs_c; - print("\t\t" "if (this == other) return EQUAL;") >> outFile_abs_c; - print("") >> outFile_abs_c; - - if (isClbRootCls) { - print("\t\t" "if (this.skirmishAIId < other.skirmishAIId) return BEFORE;") >> outFile_abs_c; - print("\t\t" "if (this.skirmishAIId > other.skirmishAIId) return AFTER;") >> outFile_abs_c; - print("\t\t" "return EQUAL;") >> outFile_abs_c; - } else { - for (ai=1; ai <= addInds_size_c; ai++) { - addIndName = addInds_c[ai]; - if ((noInterfaceIndices_c == 0) || (addIndName != noInterfaceIndices_c)) { - print("\t\t" "if (this.get" capitalize(addIndName) "() < other.get" capitalize(addIndName) "()) return BEFORE;") >> outFile_abs_c; - print("\t\t" "if (this.get" capitalize(addIndName) "() > other.get" capitalize(addIndName) "()) return AFTER;") >> outFile_abs_c; - } - } - print("\t\t" "return 0;") >> outFile_abs_c; - } - print("\t" "}") >> outFile_abs_c; - print("") >> outFile_abs_c; - } - - - # print equals(other) method - if (!isClbRootCls) { - print("\t" "@Override") >> outFile_abs_c; - print("\t" "public boolean equals(Object otherObject) {") >> outFile_abs_c; - print("") >> outFile_abs_c; - print("\t\t" "if (this == otherObject) return true;") >> outFile_abs_c; - print("\t\t" "if (!(otherObject instanceof " clsName_c ")) return false;") >> outFile_abs_c; - print("\t\t" clsName_c " other = (" clsName_c ") otherObject;") >> outFile_abs_c; - print("") >> outFile_abs_c; - - #if (isClbRootCls) { - # print("\t\t" "if (this.skirmishAIId != other.skirmishAIId) return false;") >> outFile_abs_c; - # print("\t\t" "return true;") >> outFile_abs_c; - #} - #else - { - for (ai=1; ai <= addInds_size_c; ai++) { - addIndName = addInds_c[ai]; - if ((noInterfaceIndices_c == 0) || (addIndName != noInterfaceIndices_c)) { - print("\t\t" "if (this.get" capitalize(addIndName) "() != other.get" capitalize(addIndName) "()) return false;") >> outFile_abs_c; - } - } - print("\t\t" "return true;") >> outFile_abs_c; - } - print("\t" "}") >> outFile_abs_c; - print("") >> outFile_abs_c; - } - - - # print hashCode() method - if (!isClbRootCls) { - print("\t" "@Override") >> outFile_abs_c; - print("\t" "public int hashCode() {") >> outFile_abs_c; - print("") >> outFile_abs_c; - - if (isClbRootCls) { - print("\t\t" "int _res = 0;") >> outFile_abs_c; - print("") >> outFile_abs_c; - print("\t\t" "_res += this.skirmishAIId * 10E8;") >> outFile_abs_c; - } else { - print("\t\t" "int _res = 23;") >> outFile_abs_c; - print("") >> outFile_abs_c; - # NOTE: This could go wrong if we have more then 7 additional indices - # see 10E" (7-ai) below - # the conversion to int is nessesarry, - # as otherwise it would be a double, - # which would be higher then max int, - # and most hashes would end up being max int, - # when converted from double to int - for (ai=1; ai <= addInds_size_c; ai++) { - addIndName = addInds_c[ai]; - if ((noInterfaceIndices_c == 0) || (addIndName != noInterfaceIndices_c)) { - print("\t\t" "_res += this.get" capitalize(addIndName) "() * (int) (10E" (7-ai) ");") >> outFile_abs_c; - } - } - } - print("") >> outFile_abs_c; - print("\t\t" "return _res;") >> outFile_abs_c; - print("\t" "}") >> outFile_abs_c; - print("") >> outFile_abs_c; - } - - - # print toString() method - { - print("\t" "@Override") >> outFile_abs_c; - print("\t" "public String toString() {") >> outFile_abs_c; - print("") >> outFile_abs_c; - print("\t\t" "String _res = this.getClass().toString();") >> outFile_abs_c; - print("") >> outFile_abs_c; - - #if (isClbRootCls) { # NO FOLD - # print("\t\t" "_res = _res + \"(skirmishAIId=\" + this.skirmishAIId + \", \";") >> outFile_abs_c; - #} else { # NO FOLD - # print("\t\t" "_res = _res + \"(clbHash=\" + this." myWrapVar ".hashCode() + \", \";") >> outFile_abs_c; - # print("\t\t" "_res = _res + \"skirmishAIId=\" + this." myWrapVar ".SkirmishAI_getSkirmishAIId() + \", \";") >> outFile_abs_c; - for (ai=1; ai <= addInds_size_c; ai++) { - addIndName = addInds_c[ai]; - if ((noInterfaceIndices_c == 0) || (addIndName != noInterfaceIndices_c)) { - print("\t\t" "_res = _res + \"" addIndName "=\" + this.get" capitalize(addIndName) "() + \", \";") >> outFile_abs_c; - } - } - #} # NO FOLD - print("\t\t" "_res = _res + \")\";") >> outFile_abs_c; - print("") >> outFile_abs_c; - print("\t\t" "return _res;") >> outFile_abs_c; - print("\t" "}") >> outFile_abs_c; - print("") >> outFile_abs_c; - } - } - - # make these available in called functions - implId_c_ = implId_c; - clsName_c_ = clsName_c; - printIntAndStb_c_ = printIntAndStb_c; - - # print member functions - members_size = cls_name_members[clsName_int_c ",*"]; - for (m=0; m < members_size; m++) { - memName_c = cls_name_members[clsName_int_c "," m]; - fullName_c = implId_c "," memName_c; - gsub(/,/, "_", fullName_c); - if (doWrappMember(fullName_c)) { - printMember(fullName_c, memName_c, addInds_size_c); - } else { - print("JavaOO-AIWrapper: NOTE: intentionally not wrapped: " fullName_c); - } - } - - - # finnish up - if (printIntAndStb_c) { - print("}") >> outFile_int_c; - print("") >> outFile_int_c; - close(outFile_int_c); - - print("}") >> outFile_abs_c; - print("") >> outFile_abs_c; - close(outFile_abs_c); - - print("}") >> outFile_stb_c; - print("") >> outFile_stb_c; - close(outFile_stb_c); - } - print("}") >> outFile_jni_c; - print("") >> outFile_jni_c; - close(outFile_jni_c); -} - - -function isRetParamName(paramName_rp) { - return (match(paramName_rp, /_out(_|$)/) || match(paramName_rp, /(^|_)?ret_/)); -} - - -function printMember(fullName_m, memName_m, additionalIndices_m) { - - # use some vars from the printClass function (which called us) - implId_m = implId_c_; - clsName_m = clsName_c_; - printIntAndStb_m = printIntAndStb_c_; - implCls_m = implCls_c; - clsName_int_m = clsName_int_c; - clsName_stb_m = clsName_stb_c; - clsName_jni_m = clsName_jni_c; - outFile_int_m = outFile_int_c; - outFile_stb_m = outFile_stb_c; - outFile_jni_m = outFile_jni_c; - addInds_size_m = addInds_size_c; - for (ai=1; ai <= addInds_size_m; ai++) { - addInds_m[ai] = addInds_c[ai]; - } - - indent_m = "\t"; - memId_m = clsName_m "," memName_m; - retType = cls_memberId_retType[memId_m]; # this may be changed - retType_int = retType; # this is a const var - params = cls_memberId_params[memId_m]; - isFetcher = cls_memberId_isFetcher[memId_m]; - metaComment = cls_memberId_metaComment[memId_m]; - memName = fullName_m; - sub(/^.*_/, "", memName); - functionName_m = fullName_m; - sub(/^[^_]+_/, "", functionName_m); - - if (memId_m in clsAvailInd_memberId_cls) { - return; - } - - isVoid_int_m = (retType_int == "void"); - - retVar_int_m = "_ret_int"; # this is a const var - retVar_out_m = retVar_int_m; # this may be changed - declaredVarsCode = ""; - conversionCode_pre = ""; - conversionCode_post = ""; - thrownExceptions = ""; - ommitMainCall = 0; - - if (!isVoid_int_m) { - declaredVarsCode = "\t\t" retType_int " " retVar_int_m ";" "\n" declaredVarsCode; - } - - - # Rewrite meta comment - if (match(metaComment, /FETCHER:MULTI:IDs:/)) { - # convert this: FETCHER:MULTI:IDs:Group:groupIds - # to this: ARRAY:groupIds->Group - _mc_pre = metaComment; - sub(/FETCHER:MULTI:IDs:.*$/, "", _mc_pre); - _mc_fet = metaComment; - sub(/^.*FETCHER:MULTI:IDs:/, "", _mc_fet); - sub(/[ \t].*$/, "", _mc_fet); - _mc_post = metaComment; - sub(/^.*FETCHER:MULTI:IDs:[^ \t]*/, "", _mc_post); - - _refObj = _mc_fet; - sub(/:.*$/, "", _refObj); - _refPaNa = _mc_fet; - sub(/^.*:/, "", _refPaNa); - - _mc_newArr = "ARRAY:" _refPaNa "->" _refObj; - metaComment = _mc_pre _mc_newArr _mc_post; - } - if (match(metaComment, /FETCHER:MULTI:NUM:/)) { - # convert this: FETCHER:MULTI:NUM:Resource - # to this: ARRAY:RETURN_SIZE->Resource - sub(/FETCHER:MULTI:NUM:/, "ARRAY:RETURN_SIZE->", metaComment); - } - if (match(metaComment, /REF:MULTI:/)) { - # convert this: REF:MULTI:unitDefIds->UnitDef - # to this: ARRAY:unitDefIds->UnitDef - sub(/REF:MULTI:/, "ARRAY:", metaComment); - } - - # remove additional indices from the outer params - for (ai=1; ai <= addInds_size_m; ai++) { - _removed = sub(/[^,]+(, )?/, "", params); - if (!_removed && !part_isStatic(memName_m, metaComment)) { - addIndName = addInds_m[ai]; - print("ERROR: failed removing additional indices " addIndName " from method " memName_m " in class " clsName_int_m); - exit(1); - } - } - - innerParams = removeParamTypes(params); - - # add additional indices fetcher calls to inner params - addInnerParams = ""; - addInds_real_size_m = addInds_size_m; - if (part_isStatic(memName_m, metaComment)) { - addInds_real_size_m--; - } - for (ai=1; ai <= addInds_real_size_m; ai++) { - addIndName = addInds_m[ai]; - _condComma = ""; - if (addInnerParams != "") { - _condComma = ", "; - } - addInnerParams = addInnerParams _condComma "this.get" capitalize(addIndName) "()"; - } - _condComma = ""; - if ((addInnerParams != "") && (innerParams != "")) { - _condComma = ", "; - } - innerParams = addInnerParams _condComma innerParams; - - - # convert param types - paramNames_size = split(innerParams, paramNames, ", "); - for (prm = 1; prm <= paramNames_size; prm++) { - paNa = paramNames[prm]; - if (!isRetParamName(paNa)) { - if (match(paNa, /_posF3/)) { - # convert float[3] to AIFloat3 - paNaNew = paNa; - sub(/_posF3/, "", paNaNew); - sub("float\\[\\] " paNa, "AIFloat3 " paNaNew, params); - conversionCode_pre = conversionCode_pre "\t\t" "float[] " paNa " = " paNaNew ".toFloatArray();" "\n"; - } else if (match(paNa, /_colorS3/)) { - # convert short[3] to java.awt.Color - paNaNew = paNa; - sub(/_colorS3/, "", paNaNew); - sub("short\\[\\] " paNa, "java.awt.Color " paNaNew, params); - conversionCode_pre = conversionCode_pre "\t\t" "short[] " paNa " = Util.toShort3Array(" paNaNew ");" "\n"; - } - } - } - - # convert an error return int value to an Exception - # "error-return:0=OK" - if (part_isErrorReturn(metaComment) && retType == "int") { - errorRetValueOk_m = part_getErrorReturnValueOk(metaComment); - - conversionCode_post = conversionCode_post "\t\t" "if (" retVar_out_m " != " errorRetValueOk_m ") {" "\n"; - conversionCode_post = conversionCode_post "\t\t\t" "throw new CallbackAIException(\"" memName_m "\", " retVar_out_m ");" "\n"; - conversionCode_post = conversionCode_post "\t\t" "}" "\n"; - thrownExceptions = thrownExceptions ", CallbackAIException" thrownExceptions; - - retType = "void"; - } - - # convert out params to return values - paramTypeNames_size = split(params, paramTypeNames, ", "); - hasRetParam = 0; - for (prm = 1; prm <= paramTypeNames_size; prm++) { - paNa = extractParamName(paramTypeNames[prm]); - if (isRetParamName(paNa)) { - if (retType == "void") { - paTy = extractParamType(paramTypeNames[prm]); - hasRetParam = 1; - if (match(paNa, /_posF3/)) { - # convert float[3] to AIFloat3 - retParamType = "AIFloat3"; - retVar_out_m = "_ret"; - conversionCode_pre = conversionCode_pre "\t\t" "float[] " paNa " = new float[3];" "\n"; - conversionCode_post = conversionCode_post "\t\t" retVar_out_m " = new AIFloat3(" paNa "[0], " paNa "[1]," paNa "[2]);" "\n"; - declaredVarsCode = "\t\t" retParamType " " retVar_out_m ";" "\n" declaredVarsCode; - sub("(, )?float\\[\\] " paNa, "", params); - retType = retParamType; - } else if (match(paNa, /_colorS3/)) { - retParamType = "java.awt.Color"; - retVar_out_m = "_ret"; - conversionCode_pre = conversionCode_pre "\t\t" "short[] " paNa " = new short[3];" "\n"; - conversionCode_post = conversionCode_post "\t\t" retVar_out_m " = Util.toColor(" paNa ");" "\n"; - declaredVarsCode = "\t\t" retParamType " " retVar_out_m ";" "\n" declaredVarsCode; - sub("(, )?short\\[\\] " paNa, "", params); - retType = retParamType; - } else if (match(paTy, /StringBuffer/)) { - retParamType = "String"; - retVar_out_m = "_ret"; - conversionCode_pre = conversionCode_pre "\t\t" "StringBuffer " paNa " = new StringBuffer();" "\n"; - conversionCode_post = conversionCode_post "\t\t" retParamType " " retVar_out_m " = " paNa ".toString();" "\n"; - sub("(, )?StringBuffer " paNa, "", params); - retType = retParamType; - } else { - print("FAILED converting return param: " paramTypeNames[prm] " / " fullName_m); - exit(1); - } - } else { - print("FAILED converting return param: return type should be \"void\", but is \"" retType "\""); - exit(1); - } - } - } - - # REF: - refObjs_size_m = split(metaComment, refObjs_m, "REF:"); - for (ro=2; ro <= refObjs_size_m; ro++) { - _ref = refObjs_m[ro]; - sub(/[ \t].*$/, "", _ref); # remove parts after this REF part - _isMulti = match(_ref, /MULTI:/); - _isReturn = match(_ref, /RETURN->/); - - if (!_isMulti && !_isReturn) { - # convert single param reference - _refRel = _ref; - sub(/^.*:/, "", _refRel); - _paNa = _refRel; # example: resourceId - sub(/->.*$/, "", _paNa); - _refObj = _refRel; # example: Resource - sub(/^.*->/, "", _refObj); - _paNaNew = _paNa; - if (!sub(/Id$/, "", _paNaNew)) { - _paNaNew = "oo_" _paNaNew; - } - - if (_refObj == "Team" || _refObj == "FigureGroup" || _refObj == "Path") { - print("note: ignoring meta comment: REF:" _ref); - } else { - _paNa_found = sub("int " _paNa, _refObj " " _paNaNew, params); - # it may not be found if it is an output parameter - if (_paNa_found) { - conversionCode_pre = conversionCode_pre "\t\t" "int " _paNa " = " _paNaNew ".get" _refObj "Id();" "\n"; - } - } - } else if (!_isMulti && _isReturn) { - _refObj = _ref; # example: Resource - sub(/^.*->/, "", _refObj); - - if (_refObj == "Team" || _refObj == "FigureGroup" || _refObj == "Path") { - print("note: ignoring meta comment: REF:" _ref); - continue; - } - - _implId = implId_m "," _refObj; - if (_implId in cls_implId_fullClsName) { - _fullClsName = cls_implId_fullClsName[_implId]; - } else if (cls_name_implIds[_refObj ",*"] == 1) { - _fullClsName = cls_name_implIds[_refObj ",0"]; - _fullClsName = cls_implId_fullClsName[_fullClsName]; - } else { - print("ERROR: failed finding the full class name for: " _refObj); - exit(1); - } - - _retVar_out_new = retVar_out_m "_out"; - _wrappGetInst_params = myWrapVar; - _hasRetInd = 0; - _inPa_size = split(innerParams, _, ","); - if (retType != "void" && (_inPa_size == addInds_size_m || _inPa_size == 0)) { - _hasRetInd = 1; - } - for (ai=1; ai <= (addInds_size_m-_hasRetInd); ai++) { - # Very hacky! too unmotivated for proper fix, sorry. - # proper fix would involve getting the parent of the wrapped - # class and using its additional indices - if ((functionName_m != "UnitDef_WeaponMount_getWeaponDef") && (functionName_m != "Unit_Weapon_getDef")) { - _wrappGetInst_params = _wrappGetInst_params ", " addInds_m[ai]; - } - } - if (retType != "void") { - _wrappGetInst_params = _wrappGetInst_params ", " retVar_out_m; - } else { - ommitMainCall = 1; - } - conversionCode_post = conversionCode_post "\t\t" _retVar_out_new " = Wrapp" _fullClsName ".getInstance(" _wrappGetInst_params ");" "\n"; - declaredVarsCode = "\t\t" _refObj " " _retVar_out_new ";" "\n" declaredVarsCode; - retVar_out_m = _retVar_out_new; - retType = _refObj; - } else { - print("WARNING: unsupported: REF:" _ref); - } - } - - - isMap = part_isMap(fullName_m, metaComment); - if (isMap) { - - _isFetching = 1; - _isRetSize = 0; - _isObj = 0; - _mapVar_size = "_size"; - _mapVar_keys = "keys"; - _mapVar_values = "values"; - _mapType_key = "String"; - _mapType_value = "String"; - _mapType_oo_key = "String"; - _mapType_oo_value = "String"; - _mapVar_oo = "_map"; - _mapType_int = "java.util.Map<" _mapType_oo_key ", " _mapType_oo_value ">"; - _mapType_impl = "java.util.HashMap<" _mapType_oo_key ", " _mapType_oo_value ">"; - - sub("(, )?" _mapType_key "\\[\\] " _mapVar_keys, "", params); - sub("(, )?" _mapType_value "\\[\\] " _mapVar_values, "", params); - sub(/, $/ , "", params); - - declaredVarsCode = "\t\t" "int " _mapVar_size ";" "\n" declaredVarsCode; - if (_isFetching) { - declaredVarsCode = "\t\t" _mapType_int " " _mapVar_oo ";" "\n" declaredVarsCode; - } - if (!_isRetSize) { - declaredVarsCode = "\t\t" _mapType_key "[] " _mapVar_keys ";" "\n" declaredVarsCode; - declaredVarsCode = "\t\t" _mapType_value "[] " _mapVar_values ";" "\n" declaredVarsCode; - if (_isFetching) { - conversionCode_pre = conversionCode_pre "\t\t" _mapVar_keys " = null;" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" _mapVar_values " = null;" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" _mapVar_size " = " myWrapVar "." functionName_m "(" innerParams ");" "\n"; - } else { - #conversionCode_pre = conversionCode_pre "\t\t" _arraySizeVar " = " _arrayListVar ".size();" "\n"; - #conversionCode_pre = conversionCode_pre "\t\t" "int _size = " _arraySizeVar ";" "\n"; - } - } - - if (_isRetSize) { - #conversionCode_post = conversionCode_post "\t\t" _arraySizeVar " = " retVar_out_m ";" "\n"; - #_arraySizeMaxPaNa = _arraySizeVar; - } else { - conversionCode_pre = conversionCode_pre "\t\t" _mapVar_keys " = new " _mapType_key "[" _mapVar_size "];" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" _mapVar_values " = new " _mapType_value "[" _mapVar_size "];" "\n"; - } - - if (_isFetching) { - # convert to a HashMap - conversionCode_post = conversionCode_post "\t\t" _mapVar_oo " = new " _mapType_impl "();" "\n"; - conversionCode_post = conversionCode_post "\t\t" "for (int i=0; i < " _mapVar_size "; i++) {" "\n"; -# if (_isObj) { -# if (_isRetSize) { - conversionCode_post = conversionCode_post "\t\t\t" _mapVar_oo ".put(" _mapVar_keys "[i], " _mapVar_values "[i]);" "\n"; -# } else { - #conversionCode_post = conversionCode_post "\t\t\t" _mapVar_oo ".put(" myPkgA ".Wrapp" _refObj ".getInstance(" myWrapVar _addWrappVars ", " _arrayPaNa "[i]));" "\n"; -# } -# } else if (_isNative) { - #conversionCode_post = conversionCode_post "\t\t\t" _arrayListVar ".add(" _arrayPaNa "[i]);" "\n"; -# } - conversionCode_post = conversionCode_post "\t\t" "}" "\n"; - - retParamType = _mapType_int; - retVar_out_m = _mapVar_oo; - retType = retParamType; - } else { - # convert from a HashMap - } - } - - - isArray = part_isArray(fullName_m, metaComment); - if (isArray) { - _refObj = ""; - _arrayPaNa = metaComment; - _addWrappVars = ""; - sub(/^.*ARRAY:/, "", _arrayPaNa); - sub(/[ \t].*$/, "", _arrayPaNa); - if (match(_arrayPaNa, /->/)) { - _refObj = _arrayPaNa; - sub(/->.*$/, "", _arrayPaNa); - sub(/^.*->/, "", _refObj); - _refObjInt = _refObj; - - if (match(_refObj, /-/)) { - sub(/-.*$/, "", _refObj); - sub(/^.*-/, "", _refObjInt); - } - _implId = implId_m "," _refObj; - if (_implId in cls_implId_fullClsName) { - _fullClsName = cls_implId_fullClsName[_implId]; - } else if ((myRootClass "," _refObj) in cls_implId_fullClsName) { - _implId = myRootClass "," _refObj; - _fullClsName = cls_implId_fullClsName[_implId]; - } else { - print("ERROR: failed to find the full class name for " _refObj " in " fullName_m); - exit(1); - } - _refObj = _fullClsName; - _addWrappVars = cls_implId_indicesArgs[_implId]; - sub(/(,)?[^,]*$/, "", _addWrappVars); # remove last index - _addWrappVars = trim(removeParamTypes(_addWrappVars)); - if (_addWrappVars != "") { - _addWrappVars = ", " _addWrappVars; - } - } - - _isF3 = match(_arrayPaNa, /_AposF3/); - _isObj = (_refObj != ""); - _isNative = (!_refObj && !_isObjc); - - _isRetSize = 0; - if (_isObj) { - _isRetSize = (_arrayPaNa == "RETURN_SIZE"); - } - - _arrayType = params; - sub("\\[\\][ \t]" _arrayPaNa ".*$", "", _arrayType); - sub("^.*[ \t]", "", _arrayType); - _arraySizeMaxPaNa = _arrayPaNa "_sizeMax"; - _arraySizeVar = _arrayPaNa "_size"; - _arraySizeRaw = _arrayPaNa "_raw_size"; - - _arrayListVar = _arrayPaNa "_list"; - if (_isF3) { - _arrListGenType = "AIFloat3"; - } else if (_isObj) { - _arrListGenType = _refObjInt; - } else if (_isNative) { - _arrListGenType = convertJavaBuiltinTypeToClass(_arrayType); - } - _arrListType = "java.util.List<" _arrListGenType ">"; - _arrListImplType = "java.util.ArrayList<" _arrListGenType ">"; - - _isFetching = sub("(, )?int " _arraySizeMaxPaNa, "", params); - if (_isRetSize) { - _isFetching = 1; - } else { - if (!_isFetching && !_isRetSize) { - _isNonFetcher = sub("(, )?int " _arraySizeVar, "", params); - if (!_isNonFetcher) { - print("ERROR: neither propper fetcher nor supplier ARRAY syntax in function: " fullName_m); - exit(1); - } - } - if (_isFetching) { - sub(_arrayType "\\[\\] " _arrayPaNa, "", params); - } else { - sub(_arrayType "\\[\\] " _arrayPaNa, _arrListType " " _arrayListVar, params); - } - sub(/^, /, "", params); - sub(/, $/, "", params); - } - - declaredVarsCode = "\t\t" "int " _arraySizeVar ";" "\n" declaredVarsCode; - if (_isFetching) { - declaredVarsCode = "\t\t" _arrListType " " _arrayListVar ";" "\n" declaredVarsCode; - } - if (!_isRetSize) { - declaredVarsCode = "\t\t" _arrayType "[] " _arrayPaNa ";" "\n" declaredVarsCode; - declaredVarsCode = "\t\t" "int " _arraySizeRaw ";" "\n" declaredVarsCode; - if (_isFetching) { - declaredVarsCode = "\t\t" "int " _arraySizeMaxPaNa ";" "\n" declaredVarsCode; - conversionCode_pre = conversionCode_pre "\t\t" _arraySizeMaxPaNa " = Integer.MAX_VALUE;" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" _arrayPaNa " = null;" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" _arraySizeVar " = " myWrapVar "." functionName_m "(" innerParams ");" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" _arraySizeMaxPaNa " = " _arraySizeVar ";" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" _arraySizeRaw " = " _arraySizeVar ";" "\n"; - if (_isF3) { - conversionCode_pre = conversionCode_pre "\t\t" "if (" _arraySizeVar " % 3 != 0) {" "\n"; - conversionCode_pre = conversionCode_pre "\t\t\t" "throw new RuntimeException(\"returned AIFloat3 array has incorrect size (\" + " _arraySizeVar "+ \"), should be a multiple of 3.\");" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" "}" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" _arraySizeVar " /= 3;" "\n"; - } - } else { - conversionCode_pre = conversionCode_pre "\t\t" _arraySizeVar " = " _arrayListVar ".size();" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" "int _size = " _arraySizeVar ";" "\n"; - if (_isF3) { - conversionCode_pre = conversionCode_pre "\t\t" _arraySizeVar " *= 3;" "\n"; - } - conversionCode_pre = conversionCode_pre "\t\t" _arraySizeRaw " = " _arraySizeVar ";" "\n"; - } - } - - if (_isRetSize) { - conversionCode_post = conversionCode_post "\t\t" _arraySizeVar " = " retVar_out_m ";" "\n"; - _arraySizeMaxPaNa = _arraySizeVar; - } else { - conversionCode_pre = conversionCode_pre "\t\t" _arrayPaNa " = new " _arrayType "[" _arraySizeRaw "];" "\n"; - } - - if (_isFetching) { - # convert to an ArrayList - conversionCode_post = conversionCode_post "\t\t" _arrayListVar " = new " _arrListImplType "(" _arraySizeVar ");" "\n"; - conversionCode_post = conversionCode_post "\t\t" "for (int i=0; i < " _arraySizeMaxPaNa "; i++) {" "\n"; - if (_isF3) { - conversionCode_post = conversionCode_post "\t\t\t" _arrayListVar ".add(new AIFloat3(" _arrayPaNa "[i], " _arrayPaNa "[++i], " _arrayPaNa "[++i]));" "\n"; - } else if (_isObj) { - if (_isRetSize) { - conversionCode_post = conversionCode_post "\t\t\t" _arrayListVar ".add(" myPkgA ".Wrapp" _refObj ".getInstance(" myWrapVar _addWrappVars ", i));" "\n"; - } else { - conversionCode_post = conversionCode_post "\t\t\t" _arrayListVar ".add(" myPkgA ".Wrapp" _refObj ".getInstance(" myWrapVar _addWrappVars ", " _arrayPaNa "[i]));" "\n"; - } - } else if (_isNative) { - conversionCode_post = conversionCode_post "\t\t\t" _arrayListVar ".add(" _arrayPaNa "[i]);" "\n"; - } - conversionCode_post = conversionCode_post "\t\t" "}" "\n"; - - retParamType = _arrListType; - retVar_out_m = _arrayListVar; - retType = retParamType; - } else { - # convert from an ArrayList - conversionCode_pre = conversionCode_pre "\t\t" "for (int i=0; i < _size; i++) {" "\n"; - if (_isF3) { - conversionCode_pre = conversionCode_pre "\t\t\t" "int arrInd = i*3;" "\n"; - conversionCode_pre = conversionCode_pre "\t\t\t" "AIFloat3 aif3 = " _arrayListVar ".get(i);" "\n"; - conversionCode_pre = conversionCode_pre "\t\t\t" _arrayPaNa "[arrInd] = aif3.x;" "\n"; - conversionCode_pre = conversionCode_pre "\t\t\t" _arrayPaNa "[arrInd+1] = aif3.y;" "\n"; - conversionCode_pre = conversionCode_pre "\t\t\t" _arrayPaNa "[arrInd+2] = aif3.z;" "\n"; - } else if (_isObj) { - conversionCode_pre = conversionCode_pre "\t\t\t" _arrayPaNa "[i] = " _arrayListVar ".get(i).get" _refObj "Id();" "\n"; - } else if (_isNative) { - conversionCode_pre = conversionCode_pre "\t\t\t" _arrayPaNa "[i] = " _arrayListVar ".get(i);" "\n"; - } - conversionCode_pre = conversionCode_pre "\t\t" "}" "\n"; - } - } - - firstLineEnd = ";"; - mod_m = ""; - if (!isInterface_m) { - firstLineEnd = " {"; - mod_m = "public "; - } - - sub(/^, /, "", thrownExceptions); - - print("") >> outFile_jni_m; - - isBuffered_m = !isVoid_m && isBufferedFunc(fullName_m) && (params == ""); - if (!isInterface_m && isBuffered_m) { - print(indent_m retType " _buffer_" memName ";") >> outFile_jni_m; - print(indent_m "boolean _buffer_isInitialized_" memName " = false;") >> outFile_jni_m; - } - - # print method doc comment - fullName_doc_m = fullName_m; - sub(/^[^_]*_/, "", fullName_doc_m); # remove OOAICallback_ - if (printIntAndStb_m) { - printFunctionComment_Common(outFile_int_m, funcDocComment, fullName_doc_m, indent_m); - printFunctionComment_Common(outFile_stb_m, funcDocComment, fullName_doc_m, indent_m); - } - printFunctionComment_Common(outFile_jni_m, funcDocComment, fullName_doc_m, indent_m); - - _fNoOverride = 0; - commentText = getFunctionComment_Common(funcDocComment, fullName_doc_m); - _fIsDeprecated = match(commentText, /@deprecated/); - printTripleFunc(retType, memName, params, thrownExceptions, outFile_int_m, outFile_stb_m, outFile_jni_m, printIntAndStb_m, _fNoOverride, _fIsDeprecated); - - isVoid_m = (retType == "void"); - - if (!isInterface_m) { - condRet_int_m = isVoid_int_m ? "" : retVar_int_m " = "; - indent_m = indent_m "\t"; - - if (isBuffered_m) { - print(indent_m "if (!_buffer_isInitialized_" memName ") {") >> outFile_jni_m; - indent_m = indent_m "\t"; - } - if (declaredVarsCode != "") { - print(declaredVarsCode) >> outFile_jni_m; - } - if (conversionCode_pre != "") { - print(conversionCode_pre) >> outFile_jni_m; - } - if (!ommitMainCall) { - print(indent_m condRet_int_m myWrapVar "." functionName_m "(" innerParams ");") >> outFile_jni_m; - } - if (conversionCode_post != "") { - print(conversionCode_post) >> outFile_jni_m; - } - if (isBuffered_m) { - print(indent_m "_buffer_" memName " = " retVar_out_m ";") >> outFile_jni_m; - print(indent_m "_buffer_isInitialized_" memName " = true;") >> outFile_jni_m; - sub(/\t/, "", indent_m); - print(indent_m "}") >> outFile_jni_m; - print("") >> outFile_jni_m; - retVar_out_m = "_buffer_" memName; - } - if (!isVoid_m) { - print(indent_m "return " retVar_out_m ";") >> outFile_jni_m; - } - sub(/\t/, "", indent_m); - print(indent_m "}") >> outFile_jni_m; - } -} - - -function doWrappMember(fullName_dwm) { - - doWrapp_dwm = 1; - - return doWrapp_dwm; -} - -# Used by the common OO AWK script -function doWrappOO(funcFullName_dw, params_dw, metaComment_dw) { - - doWrapp_dw = 1; - - #doWrapp_dw = doWrapp_dw && !match(funcFullName_dw, /Lua_callRules/) && !match(funcFullName_dw, /Lua_callUI/); - - return doWrapp_dw; -} - -function wrappFunctionDef(funcDef, commentEolTot) { - - size_funcParts = split(funcDef, funcParts, "(\\()|(\\);)"); - # because the empty part after ");" would count as part as well - size_funcParts--; - - fullName = funcParts[1]; - fullName = trim(fullName); - sub(/.*[ \t]+/, "", fullName); - - retType = funcParts[1]; - sub(/[ \t]*public/, "", retType); - sub(fullName, "", retType); - retType = trim(retType); - - params = funcParts[2]; - - wrappFunctionPlusMeta(retType, fullName, params, commentEolTot); -} - -# This function has to return true (1) if a doc comment (eg: /** foo bar */) -# can be deleted. -# If there is no special condition you want to apply, -# it should always return true (1), -# cause there are additional mechanism to prevent accidental deleting. -# see: commonDoc.awk -function canDeleteDocumentation() { - return isMultiLineFunc != 1; -} - - -# grab callback functions info -# 2nd, 3rd, ... line of a function definition -{ - if (isMultiLineFunc) { # function is defined on one single line - funcIntermLine = $0; - # separate possible comment at end of line: // fu bar - commentEol = funcIntermLine; - if (sub(/.*\/\//, "", commentEol)) { - commentEolTot = commentEolTot commentEol; - } - sub(/[ \t]*\/\/.*$/, "", funcIntermLine); - funcIntermLine = trim(funcIntermLine); - funcSoFar = funcSoFar " " funcIntermLine; - if (match(funcSoFar, /;$/)) { - # function ends in this line - wrappFunctionDef(funcSoFar, commentEolTot); - isMultiLineFunc = 0; - } - } -} -# 1st line of a function definition -/\tpublic .*\);/ { - - funcStartLine = $0; - # separate possible comment at end of line: // foo bar - commentEolTot = ""; - commentEol = funcStartLine; - if (sub(/.*\/\//, "", commentEol)) { - commentEolTot = commentEolTot commentEol; - } - # remove possible comment at end of line: // foo bar - sub(/\/\/.*$/, "", funcStartLine); - funcStartLine = trim(funcStartLine); - if (match(funcStartLine, /;$/)) { - # function ends in this line - wrappFunctionDef(funcStartLine, commentEolTot); - } else { - funcSoFar = funcStartLine; - isMultiLineFunc = 1; - } -} - - - -END { - # finalize things - store_everything(); - printClasses(); -} diff --git a/AI/Wrappers/JavaOO/bin/wrappEvents.awk b/AI/Wrappers/JavaOO/bin/wrappEvents.awk deleted file mode 100755 index d044b135740..00000000000 --- a/AI/Wrappers/JavaOO/bin/wrappEvents.awk +++ /dev/null @@ -1,627 +0,0 @@ -#!/usr/bin/awk -f -# -# This awk script creates a java class in OO style to wrap the C style -# JNI based AI Events wrapper interface. -# In other words, the output of this file wraps: -# com/springrts/ai/AI.java -# which wraps: -# rts/ExternalAI/Interface/AISEvents.h -# -# This script uses functions from the following files: -# * common.awk -# * commonDoc.awk -# * commonOOCallback.awk -# Variables that can be set on the command-line (with -v): -# * GENERATED_SOURCE_DIR : the generated sources root dir -# * JAVA_GENERATED_SOURCE_DIR : the generated java sources root dir -# * INTERFACE_SOURCE_DIR : the Java AI Interfaces static source files root dir -# * INTERFACE_GENERATED_SOURCE_DIR : the Java AI Interfaces generated source files root dir -# - -BEGIN { - # initialize things - - # define the field splitter(-regex) - FS = "(\\()|(\\);)"; - IGNORECASE = 0; - - # Used by other scripts - JAVA_MODE = 1; - - # These vars can be assigned externally, see file header. - # Set the default values if they were not supplied on the command line. - if (!GENERATED_SOURCE_DIR) { - GENERATED_SOURCE_DIR = "../src-generated/main"; - } - if (!JAVA_GENERATED_SOURCE_DIR) { - JAVA_GENERATED_SOURCE_DIR = GENERATED_SOURCE_DIR "/java"; - } - if (!INTERFACE_SOURCE_DIR) { - INTERFACE_SOURCE_DIR = "../../../Interfaces/Java/src/main/java"; - } - if (!INTERFACE_GENERATED_SOURCE_DIR) { - INTERFACE_GENERATED_SOURCE_DIR = "../../../Interfaces/Java/src-generated/main/java"; - } - - javaSrcRoot = "../src/main/java"; - - myParentPkgA = "com.springrts.ai"; - myMainPkgA = myParentPkgA ".oo"; - myPkgClbA = myMainPkgA ".clb"; - myPkgEvtA = myMainPkgA ".evt"; - myMainPkgD = convertJavaNameFormAToD(myMainPkgA); - myPkgEvtD = convertJavaNameFormAToD(myPkgEvtA); - - aiFloat3Class = "AIFloat3"; - - myOOAIClass = "OOAI"; - myOOAIInterface = "I" myOOAIClass; - myOOAIAbstractClass = "AbstractOOAI"; - myOOEventAIClass = "OOEventAI"; - myOOEventAIInterface = "I" myOOEventAIClass; - myOOAIFile = JAVA_GENERATED_SOURCE_DIR "/" myMainPkgD "/" myOOAIClass ".java"; - myOOAIInterfaceFile = JAVA_GENERATED_SOURCE_DIR "/" myMainPkgD "/" myOOAIInterface ".java"; - myOOAIAbstractFile = JAVA_GENERATED_SOURCE_DIR "/" myMainPkgD "/" myOOAIAbstractClass ".java"; - myOOEventAIFile = JAVA_GENERATED_SOURCE_DIR "/" myMainPkgD "/" myOOEventAIClass ".java"; - myOOEventAIInterfaceFile = JAVA_GENERATED_SOURCE_DIR "/" myMainPkgD "/" myOOEventAIInterface ".java"; - - printOOAIHeader(myOOAIFile, myOOAIClass); - printOOAIHeader(myOOAIInterfaceFile, myOOAIInterface); - printOOAIHeader(myOOAIAbstractFile, myOOAIAbstractClass); - printOOEventAIHeader(myOOEventAIFile); - printOOEventAIHeader(myOOEventAIInterfaceFile); - - ind_evt = 0; -} - - - -function printOOAIHeader(outFile, clsName) { - - printCommentsHeader(outFile); - print("") >> outFile; - print("package " myMainPkgA ";") >> outFile; - print("") >> outFile; - print("") >> outFile; - if (clsName != myOOAIInterface) { - print("import " myParentPkgA ".AI;") >> outFile; - } - if (clsName == myOOAIClass) { - print("import " myParentPkgA ".AICallback;") >> outFile; - print("import " myMainPkgA ".clb.WrappOOAICallback;") >> outFile; - print("import " myMainPkgA ".clb.WrappUnit;") >> outFile; - print("import " myMainPkgA ".clb.WrappWeaponDef;") >> outFile; - } - print("import " myMainPkgA ".AIFloat3;") >> outFile; - print("import " myMainPkgA ".clb.OOAICallback;") >> outFile; - print("import " myMainPkgA ".clb.Unit;") >> outFile; - print("import " myMainPkgA ".clb.WeaponDef;") >> outFile; - print("") >> outFile; - print("/**") >> outFile; - print(" * TODO: Add description here") >> outFile; - print(" *") >> outFile; - print(" * @author hoijui") >> outFile; - print(" * @version GENERATED") >> outFile; - print(" */") >> outFile; - - _type = "abstract class"; - _extends = ""; - _implements = " implements " myOOAIInterface ", AI"; - if (clsName == myOOAIAbstractClass) { - _extends = " extends " myOOAIClass; - } - if (clsName == myOOAIInterface) { - _type = "interface"; - _implements = ""; - } - print("public " _type " " clsName _extends _implements " {") >> outFile; - print("") >> outFile; - - if (clsName == myOOAIClass) { - print("\t" "private AICallback clb = null;") >> outFile; - print("\t" "private OOAICallback clbOO = null;") >> outFile; - print("") >> outFile; - } -} -function printOOAIEnd(outFile) { - - print("}") >> outFile; - print("") >> outFile; -} - -function printOOEventAIHeader(outFile) { - - printCommentsHeader(outFile); - print("") >> outFile; - print("package " myMainPkgA ";") >> outFile; - print("") >> outFile; - print("") >> outFile; - print("import " myParentPkgA ".AI;") >> outFile; - print("import " myMainPkgA "." myOOAIClass ";") >> outFile; - print("import " myPkgEvtA ".*;") >> outFile; - print("import " myPkgClbA ".*;") >> outFile; - print("") >> outFile; - print("/**") >> outFile; - print(" * TODO: Add description here") >> outFile; - print(" *") >> outFile; - print(" * @author hoijui") >> outFile; - print(" * @version GENERATED") >> outFile; - print(" */") >> outFile; - if (outFile == myOOEventAIFile) { - print("public abstract class " myOOEventAIClass " extends " myOOAIClass " implements " myOOEventAIInterface ", AI {") >> outFile; - } else { - print("public interface " myOOEventAIInterface " {") >> outFile; - } - print("") >> outFile; - - print("\t" "/**") >> outFile; - print("\t" " * TODO: Add description here") >> outFile; - print("\t" " *") >> outFile; - print("\t" " * @param event the AI event to handle, sent by the engine") >> outFile; - print("\t" " * @throws AIException") >> outFile; - print("\t" " */") >> outFile; - _modifyer = ""; - if (outFile == myOOEventAIFile) { - _modifyer = " abstract"; - } - print("\t" "public" _modifyer " void handleEvent(AIEvent event) throws EventAIException;") >> outFile; - print("") >> outFile; -} -function printOOEventAIEnd(outFile) { - - print("}") >> outFile; - print("") >> outFile; -} - - -function convertJavaSimpleTypeToOO(paType_sto, paName_sto, - paTypeNew_sto, paNameNew_sto, convPre_sto, convPost_sto) { - - _change = 1; - - # uses this global vars: - # - paramTypeNew - # - paramNameNew - # - conversionCode_pre - # - conversionCode_post - - paramTypeNew = paType_sto; - paramNameNew = paName_sto; - - if (match(paName_sto, /_posF3/)) { - # convert float[3] to AIFloat3 - sub(/_posF3/, "", paramNameNew); - paramTypeNew = "AIFloat3"; - conversionCode_pre = conversionCode_pre "\t\t" paramTypeNew " " paramNameNew " = new " paramTypeNew "(" paName_sto ");" "\n"; - #conversionCode_pre = conversionCode_pre "\t\t" paType_sto " " paName_sto " = " paNameNew_sto ".toFloatArray();" "\n"; - } else if (match(paName_sto, /_colorS3/)) { - # convert short[3] to java.awt.Color - sub(/_colorS3/, "", paramNameNew); - paramTypeNew = "java.awt.Color"; - conversionCode_pre = conversionCode_pre "\t\t" paramTypeNew " " paramNameNew " = Util.toColor(" paName_sto ");" "\n"; - #conversionCode_pre = conversionCode_pre "\t\t" paType_sto " " paName_sto " = Util.toShort3Array(" paNameNew_sto ");" "\n"; - } else if ((paType_sto == "int") && match(paName_sto, /(unit|builder|attacker|enemy)(Id)?$/)) { - # convert int to Unit - sub(/Id$/, "", paramNameNew); - paramNameNew = "oo_" paramNameNew; - paramTypeNew = "Unit"; - conversionCode_pre = conversionCode_pre "\t\t" paramTypeNew " " paramNameNew " = Wrapp" paramTypeNew ".getInstance(this.clb, " paName_sto ");" "\n"; - } else if ((paType_sto == "int[]") && match(paName_sto, /unit(s|Ids)$/)) { - # convert int[] to List - sub(/(Id)?s$/, "s", paramNameNew); - paramNameNew = "oo_" paramNameNew; - paramTypeNew = "java.util.List"; - conversionCode_pre = conversionCode_pre "\t\t" paramTypeNew " " paramNameNew " = new java.util.ArrayList();" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" "for (int u=0; u < " paName_sto ".length; u++) {" "\n"; - conversionCode_pre = conversionCode_pre "\t\t\t" paramNameNew ".add(WrappUnit.getInstance(this.clb, " paName_sto "[u]));" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" "}" "\n"; - } else if ((paType_sto == "int") && match(paName_sto, /(weaponDef)(Id)?$/)) { - # convert int to WeaponDef - sub(/Id$/, "", paramNameNew); - paramNameNew = "oo_" paramNameNew; - paramTypeNew = "WeaponDef"; - conversionCode_pre = conversionCode_pre "\t\t" paramTypeNew " " paramNameNew " = Wrapp" paramTypeNew ".getInstance(this.clb, " paName_sto ");" "\n"; - } else if (paType_sto == "AICallback") { - # convert AICallback to OOAICallback - paramNameNew = "oo_" paramNameNew; - paramTypeNew = "OOAICallback"; - conversionCode_pre = conversionCode_pre "\t\t" "this.clb = " paName_sto ";" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" "this.clbOO = Wrapp" paramTypeNew ".getInstance(" paName_sto ");" "\n"; - conversionCode_pre = conversionCode_pre "\t\t" paramTypeNew " " paramNameNew " = this.clbOO;" "\n"; - } else { - _change = 0; - } - - return _change; -} - - -function printEventsOO() { - - # agarra te los event interfaces - for (e=0; e < evts_size; e++) { - meta_es = evts_meta[e]; - - interfList_size_es = 0; - if (match(meta_es, /INTERFACES:/)) { - interfMeta_es = meta_es; - sub(/^.*INTERFACES:/, "", interfMeta_es); - sub(/[ \t].*$/, "", interfMeta_es); - interfList_size_es = split(interfMeta_es, interfList_es, /\),/); - - for (i=1; i <= interfList_size_es; i++) { - _intName = interfList_es[i]; - sub(/\(.*$/, "", _intName); - _intParams = interfList_es[i]; - sub(/^.*\(/, "", _intParams); - sub(/\)$/, "", _intParams); - - if (!(_intName in int_names)) { - int_names[_intName] = _intParams; - } - evts_intNames[e "," (i-1)] = _intName; - evts_intParams[e "," (i-1)] = _intParams; - } - } - evts_intNames[e ",*"] = interfList_size_es; - } - - # print the event classes - for (e=0; e < evts_size; e++) { - printEventOO(e); - } - - # print the event interfaces - for (intName in int_names) { - printOOEventInterface(intName); - } -} - -function printEventOO(ind_evt_em) { - - retType_em = evts_retType[ind_evt_em]; - name_em = evts_name[ind_evt_em]; - params_em = evts_params[ind_evt_em]; - meta_em = evts_meta[ind_evt_em]; - - paramsList_size_em = split(params_em, paramsList_em, ", "); - - conversionCode_pre = ""; - conversionCode_post = ""; - ooParams_em = ""; - - for (p=1; p <= paramsList_size_em; p++) { - _param = paramsList_em[p]; - _paramType = _param; - sub(/ [^ ]*$/, "", _paramType); - _paramName = _param; - sub(/^[^ ]* /, "", _paramName); - - paramTypeNew = ""; - paramNameNew = ""; - convertJavaSimpleTypeToOO(_paramType, _paramName); - - if (!match(_paramName, /_size$/)) { - ooParams_em = ooParams_em ", " paramTypeNew " " paramNameNew; - } - } - sub(/^, /, "", ooParams_em); - - _equalMethod = (ooParams_em == params_em); - _isVoid = (retType_em == "void"); - if (_isVoid) { - _condRet = ""; - } else { - _condRet = "_ret = "; - } - - print("") >> myOOAIFile; - - if (!_equalMethod) { - ooParamNames_em = removeParamTypes(ooParams_em); - print("\t" "@Override") >> myOOAIFile; - print("\t" "public final " retType_em " " name_em "(" params_em ") {") >> myOOAIFile; - print("") >> myOOAIFile; - - if (!_isVoid) { - print("\t\t" retType_em " _ret;") >> myOOAIFile; - print("") >> myOOAIFile; - } - if (conversionCode_pre != "") { - print(conversionCode_pre) >> myOOAIFile; - } - - print("\t\t" _condRet "this." name_em "(" ooParamNames_em ");") >> myOOAIFile; - - if (conversionCode_post != "") { - print(conversionCode_post) >> myOOAIFile; - } - if (!_isVoid) { - print("") >> myOOAIFile; - print("\t\t" "return _ret;") >> myOOAIFile; - } - - print("\t" "}") >> myOOAIFile; - } - - printFunctionComment_Common(myOOAIFile, evts_docComment, ind_evt_em, "\t"); - print("\t" "@Override") >> myOOAIFile; - if (retType_em == "int") { - print("\t" "public " retType_em " " name_em "(" ooParams_em ") { return 0; }") >> myOOAIFile; - } else { - print("Warning: No default return value given for event return-type " retType_em); - print("\t" "public abstract " retType_em " " name_em "(" ooParams_em ");") >> myOOAIFile; - } - - ooParamsCleaned_em = ooParams_em; - gsub(/ oo_/, " ", ooParamsCleaned_em); - - print("") >> myOOAIInterfaceFile; - printFunctionComment_Common(myOOAIInterfaceFile, evts_docComment, ind_evt_em, "\t"); - print("\t" "public " retType_em " " name_em "(" ooParamsCleaned_em ");") >> myOOAIInterfaceFile; - - print("") >> myOOAIAbstractFile; - printFunctionComment_Common(myOOAIAbstractFile, evts_docComment, ind_evt_em, "\t"); - print("\t" "@Override") >> myOOAIAbstractFile; - print("\t" "public " retType_em " " name_em "(" ooParamsCleaned_em ") {") >> myOOAIAbstractFile; - print("\t\t" "return 0; // signaling: OK") >> myOOAIAbstractFile; - print("\t" "}") >> myOOAIAbstractFile; - - printOOEventWrapper(retType_em, name_em, ooParamsCleaned_em, meta_em, ind_evt_em); -} - -function printOOEventWrapper(retType_ei, mthName_ei, ooParams_ei, meta_ei, ind_evt_ei) { - - outFile = myOOEventAIFile; - ooParamsNoTypes_ei = removeParamTypes(ooParams_ei); - evtName_ei = capitalize(mthName_ei) "AIEvent"; - - print("\t" "@Override") >> outFile; - print("\t" "public final " retType_ei " " mthName_ei "(" ooParams_ei ") {") >> outFile; - print("") >> outFile; - print("\t\t" "AIEvent evt = new " evtName_ei "(" ooParamsNoTypes_ei ");") >> outFile; - print("\t\t" "try {") >> outFile; - print("\t\t\t" "this.handleEvent(evt);") >> outFile; - print("\t\t\t" "return 0; // everything OK") >> outFile; - print("\t\t" "} catch (EventAIException ex) {") >> outFile; - print("\t\t\t" "return ex.getErrorNumber();") >> outFile; - print("\t\t" "}") >> outFile; - print("\t" "}") >> outFile; - print("") >> outFile; - - printOOEventClass(retType_ei, evtName_ei, ooParams_ei, meta_ei, ind_evt_ei); -} - -function printOOEventClass(retType_ec, evtName_ec, ooParams_ec, meta_ec, ind_evt_ec) { - - outFile = JAVA_GENERATED_SOURCE_DIR "/" myPkgEvtD "/" evtName_ec ".java"; - - ooParamsList_size_ec = split(ooParams_ec, ooParamsList_ec, ", "); - int_size_ec = evts_intNames[ind_evt_ec ",*"]; - - # list up interface parameters - # clear arrays - split("", intParams_toPrint_ec); - split("", myIntParams_intParams_ec); - split("", myIntParams_int_ec); - for (_ii=0; _ii < int_size_ec; _ii++) { - _name = evts_intNames[ind_evt_ec "," _ii]; - _myIntParams = evts_intParams[ind_evt_ec "," _ii]; - _intParams = int_names[_name]; - - _myIntParamsList_size = split(_myIntParams, _myIntParamsList, ","); - _intParamsList_size = split(_intParams, _intParamsList, ","); - - for (_p=1; _p <= _intParamsList_size; _p++) { - _myIntParam = _myIntParamsList[_p]; - _intParam = _intParamsList[_p]; - - intParams_toPrint_ec[_intParam] = 1; - myIntParams_int_ec[_myIntParam] = _name; - if (_myIntParam != _intParam) { - myIntParams_intParams_ec[_myIntParam] = _intParam; - } - } - } - - _addIntLst = ""; - for (i=0; i < int_size_ec; i++) { - _addIntLst = _addIntLst ", " evts_intNames[ind_evt_ec "," i] "AIEvent"; - } - - # print comments header - printCommentsHeader(outFile); - print("") >> outFile; - - print("package " myPkgEvtA ";") >> outFile; - print("") >> outFile; - print("") >> outFile; - - # print imports - print("import " myMainPkgA ".AIEvent;") >> outFile; - print("import " myMainPkgA ".AIFloat3;") >> outFile; - print("import " myPkgClbA ".*;") >> outFile; - print("") >> outFile; - - # print class header - printFunctionComment_Common(outFile, evts_docComment, ind_evt_ec, ""); - print("public class " evtName_ec " implements AIEvent" _addIntLst " {") >> outFile; - print("") >> outFile; - - # print member vars - for (_p=1; _p <= ooParamsList_size_ec; _p++) { - print("\t" "private " ooParamsList_ec[_p] ";") >> outFile; - } - print("") >> outFile; - - # print constructor - print("\t" "public " evtName_ec "(" ooParams_ec ") {") >> outFile; - print("") >> outFile; - for (_p=1; _p <= ooParamsList_size_ec; _p++) { - _name = extractParamName(ooParamsList_ec[_p]); - print("\t\t" "this." _name " = " _name ";") >> outFile; - } - print("\t" "}") >> outFile; - print("") >> outFile; - - # print member getters - for (_p=1; _p <= ooParamsList_size_ec; _p++) { - _type = extractParamType(ooParamsList_ec[_p]); - _name = extractParamName(ooParamsList_ec[_p]); - - # save the type for later writing of interfaces, - # in case it is a ninterface param - if (_name in intParams_toPrint_ec) { - _intName = _name; - if (_name in myIntParams_intParams_ec) { - _intName = myIntParams_intParams_ec[_name]; - } - _int = myIntParams_int_ec[_name]; - - int_name_param_type[_int "," _intName] = _type; - # hacky - int_name_param_type[_int "," _name] = _type; - } - - # print out @Override if this is an interface member - if (_name in intParams_toPrint_ec && intParams_toPrint_ec[_name] == 1) { - print("\t" "@Override") >> outFile; - intParams_toPrint_ec[_name] = 0; - } - - print("\t" "public " _type " get" capitalize(_name) "() {") >> outFile; - print("\t\t" "return this." _name ";") >> outFile; - print("\t" "}") >> outFile; - - # print out another getter if the interface param name is different - if (_name in myIntParams_intParams_ec) { - _intName = myIntParams_intParams_ec[_name]; - intParams_toPrint_ec[_intName] = 0; - - print("\t" "@Override") >> outFile; - print("\t" "public " _type " get" capitalize(_intName) "() {") >> outFile; - print("\t\t" "return this.get" capitalize(_name) "();") >> outFile; - print("\t" "}") >> outFile; - } - - print("") >> outFile; - } - - print("}") >> outFile; - - close(outFile); -} - -function printOOEventInterface(int_name_ei) { - - outFile = JAVA_GENERATED_SOURCE_DIR "/" myPkgEvtD "/" int_name_ei "AIEvent.java"; - - int_params_ei = int_names[int_name_ei]; - - int_paramsList_size_ei = split(int_params_ei, int_paramsList_ei, ","); - int_size_ei = evts_intNames[ind_evt_ec ",*"]; - - _addIntLst = ""; - for (i=0; i < int_size_ei; i++) { - _addIntLst = _addIntLst ", " evts_intNames[ind_evt_ec "," i] "AIEvent"; - } - - # print comments header - printCommentsHeader(outFile); - print("") >> outFile; - - print("package " myPkgEvtA ";") >> outFile; - print("") >> outFile; - print("") >> outFile; - - # print imports - print("import " myMainPkgA ".AIEvent;") >> outFile; - print("import " myMainPkgA ".AIFloat3;") >> outFile; - print("import " myPkgClbA ".*;") >> outFile; - print("") >> outFile; - - # print class header - print("public interface " int_name_ei "AIEvent extends AIEvent {") >> outFile; - print("") >> outFile; - - # print getters - for (_p=1; _p <= int_paramsList_size_ei; _p++) { - _name = int_paramsList_ei[_p]; - _type = int_name_param_type[int_name_ei "," _name]; - - print("\t" "public " _type " get" capitalize(_name) "();") >> outFile; - } - print("") >> outFile; - - print("}") >> outFile; -} - - - -# This function has to return true (1) if a doc comment (eg: /** foo bar */) -# can be deleted. -# If there is no special condition you want to apply, -# it should always return true (1), -# cause there are additional mechanism to prevent accidental deleting. -# see: commonDoc.awk -function canDeleteDocumentation() { - return 1; -} - -################################################################################ -### BEGIN: parsing and saving the event methods - -# beginning of struct S*Event -/^\tpublic .+\);/ { - - _head = $1; - _params = $2; - _tail = $3; - - _retType = _head; - sub(/^\tpublic /, "", _retType); # remove pre - sub(/ .*$/, "", _retType); # remove post - - _name = _head; - sub(/^\tpublic [^ ]+ /, "", _name); # remove pre - - _meta = $0; - _hasMeta = sub(/.*\);[ \t]*\/\/[ \t]*/, "", _meta); # remove pre - if (!_hasMeta) { - _meta = ""; - } - - evts_retType[ind_evt] = _retType; - evts_name[ind_evt] = _name; - evts_params[ind_evt] = _params; - evts_meta[ind_evt] = _meta; - storeDocLines(evts_docComment, ind_evt); -#print(_retType " " _name "(" _params ") // " _meta); - ind_evt++; -} - -### END: parsing and saving the event methods -################################################################################ - - - - -END { - # finalize things - - evts_size = ind_evt; - - printEventsOO(); - - printOOAIEnd(myOOAIFile); - printOOAIEnd(myOOAIInterfaceFile); - printOOAIEnd(myOOAIAbstractFile); - printOOEventAIEnd(myOOEventAIFile); - printOOEventAIEnd(myOOEventAIInterfaceFile); - - close(myOOAIFile); - close(myOOAIInterfaceFile); - close(myOOAIAbstractFile); - close(myOOEventAIFile); - close(myOOEventAIInterfaceFile); -} diff --git a/AI/Wrappers/JavaOO/jlib/vecmath-src.jar b/AI/Wrappers/JavaOO/jlib/vecmath-src.jar deleted file mode 100644 index 4f66594635f784da70817fee98ebc6bb52db0d5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 183017 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%0DqRNwh+>h1nLb1k)uYG&jotYyuUgY-9d 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

~$9-_LL;s_CJzdidj86~|=WrK@9 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%0DS~Nj{|5_2uNK_joS(Ay8~=U}Kr*b_+n2ln($a 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 - 4.0.0 - - - - - - 0.1 - - - - com.springrts - common-spring - 1.0 - ../../../rts/build/maven/support/common/spring/pom.xml - - - com.springrts - ai-wrapper-javaoo - ${my.version} - - jar - - Java OO AI Wrapper - Java Object Oriented Artificial Intelligence interface wrapper for the Spring RTS engine - https://springrts.com/wiki/AIWrapper:JavaOO - 2008 - - - - - scm:git:git://github.com/spring/spring.git - scm:git:git@github.com:spring/spring.git - http://github.com/spring/spring/tree/master/AI/Wrappers/JavaOO/ - - - - - - - com.springrts - ai-interface-java - ${my.version} - - - - java3d - vecmath - 1.3.1 - - - - - diff --git a/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/AIEvent.java b/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/AIEvent.java deleted file mode 100644 index 796112ef8a1..00000000000 --- a/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/AIEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - Copyright (c) 2008 Robin Vobruba - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.springrts.ai.oo; - -/** - * An AI event is sent form the engine to Java Skirmish AIs. - * - * @author hoijui - * @version 0.1 - */ -public interface AIEvent { - -} diff --git a/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/AIException.java b/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/AIException.java deleted file mode 100644 index 2c3a9ddb927..00000000000 --- a/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/AIException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - Copyright (c) 2009 Robin Vobruba - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.springrts.ai.oo; - -/** - * Common base interface for AI related Exceptions. - * - * @author hoijui - * @version 0.1 - */ -public interface AIException { - - /** - * Returns the error associated with this Exception. - * This is used to send over low-level language interfaces, - * for example C, where exceptions are not supported. - * @return should be != 0, as this value is reserved for the no-error state - */ - public int getErrorNumber(); -} diff --git a/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/AIFloat3.java b/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/AIFloat3.java deleted file mode 100644 index 7865eac461c..00000000000 --- a/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/AIFloat3.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - Copyright (c) 2008 Robin Vobruba - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.springrts.ai.oo; - - -import javax.vecmath.Tuple3d; -import javax.vecmath.Tuple3f; -import javax.vecmath.Vector3d; -import javax.vecmath.Vector3f; -import java.awt.Color; - -/** - * Represents a position on the map. - * - * @author hoijui.quaero@gmail.com - * @version 0.1 - */ -public class AIFloat3 extends Vector3f { - - public AIFloat3() { - super(0.0f, 0.0f, 0.0f); - } - public AIFloat3(float x, float y, float z) { - super(x, y, z); - } - public AIFloat3(float[] xyz) { - super(xyz); - } - public AIFloat3(AIFloat3 other) { - super(other); - } - public AIFloat3(Tuple3d tub3d) { - super(tub3d); - } - public AIFloat3(Tuple3f tub3f) { - super(tub3f); - } - public AIFloat3(Vector3d vec3d) { - super(vec3d); - } - public AIFloat3(Vector3f vec3f) { - super(vec3f); - } - public AIFloat3(Color color) { - - this.x = color.getRed() / 255.0F; - this.y = color.getGreen() / 255.0F; - this.z = color.getBlue() / 255.0F; - } - - public Color toColor() { - return new Color(x, y, z); - } - @Override - public String toString() { - return "(" + this.x + ", " + this.y + ", " + this.z + ")"; - } - public float[] toFloatArray() { - - float[] floatArr = new float[3]; - loadInto(floatArr); - return floatArr; - } - public void loadInto(float[] xyz) { - - xyz[0] = x; - xyz[1] = y; - xyz[2] = z; - } - - @Override - public int hashCode() { - - final int prime = 31; - int result = super.hashCode(); - result = prime * result + Float.floatToIntBits(x); - result = prime * result + Float.floatToIntBits(y); - result = prime * result + Float.floatToIntBits(z); - return result; - } - @Override - public boolean equals(Object obj) { - - if (this == obj) { - return true; - } else if (!super.equals(obj)) { - return false; - } else if (getClass() != obj.getClass()) { - return false; - } - - AIFloat3 other = (AIFloat3) obj; - if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x)) { - return false; - } else if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y)) { - return false; - } else if (Float.floatToIntBits(z) != Float.floatToIntBits(other.z)) { - return false; - } else { - return true; - } - } -} diff --git a/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/CallbackAIException.java b/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/CallbackAIException.java deleted file mode 100644 index 309663ef976..00000000000 --- a/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/CallbackAIException.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - Copyright (c) 2009 Robin Vobruba - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.springrts.ai.oo; - -/** - * An exception of this type may be thrown while from an AI callback method. - * - * @author hoijui - */ -public class CallbackAIException extends RuntimeException implements AIException { - - private String methodName; - private int errorNumber; - - public CallbackAIException(String methodName, int errorNumber) { - super("Error calling method \"" + methodName + "\": " + errorNumber); - - this.methodName = methodName; - this.errorNumber = errorNumber; - } - public CallbackAIException(String methodName, int errorNumber, Throwable cause) { - super("Error calling method \"" + methodName + "\": " + errorNumber, cause); - - this.methodName = methodName; - this.errorNumber = errorNumber; - } - - /** - * Returns the name of the method in which the exception occurred. - */ - public String getMethodName() { - return methodName; - } - - /** - * Returns the error number that will be sent to the engine, - * and consequently appear in the engines main log file. - * @return should be != 0, as this value is reserved for the no-error state - */ - @Override - public int getErrorNumber() { - return errorNumber; - } -} diff --git a/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/EventAIException.java b/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/EventAIException.java deleted file mode 100644 index 8486a33f23f..00000000000 --- a/AI/Wrappers/JavaOO/src/main/java/com/springrts/ai/oo/EventAIException.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright (c) 2009 Robin Vobruba - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package com.springrts.ai.oo; - -/** - * An exception of this type may be thrown while handling an AI event. - * - * @author hoijui - * @version 0.1 - */ -public class EventAIException extends Exception implements AIException { - - public static final int DEFAULT_ERROR_NUMBER = 10; - - private int errorNumber; - - public EventAIException() { - super(); - - this.errorNumber = DEFAULT_ERROR_NUMBER; - } - public EventAIException(int errorNumber) { - super(); - - this.errorNumber = errorNumber; - } - - public EventAIException(String message) { - super(message); - - this.errorNumber = DEFAULT_ERROR_NUMBER; - } - public EventAIException(String message, int errorNumber) { - super(message); - - this.errorNumber = errorNumber; - } - - public EventAIException(String message, Throwable cause) { - super(message, cause); - - this.errorNumber = DEFAULT_ERROR_NUMBER; - } - public EventAIException(String message, Throwable cause, int errorNumber) { - super(message, cause); - - this.errorNumber = errorNumber; - } - - public EventAIException(Throwable cause) { - super(cause); - - this.errorNumber = DEFAULT_ERROR_NUMBER; - } - public EventAIException(Throwable cause, int errorNumber) { - super(cause); - - this.errorNumber = errorNumber; - } - - /** - * Returns the error number that will be sent to the engine, - * and consequently appear in the engines main log file. - * @return should be != 0, as this value is reserved for the no-error state - */ - @Override - public int getErrorNumber() { - return errorNumber; - } -} diff --git a/CMakeLists.txt b/CMakeLists.txt index d016d27287c..243ef8cb697 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,11 +73,6 @@ else() set(DEBUG_BUILD FALSE) endif() -if(DEBUG_BUILD) - set(JAVA_COMPILE_FLAG_CONDITIONAL "-g:lines,source,vars") -else() - set(JAVA_COMPILE_FLAG_CONDITIONAL "-g:lines,source") -endif() # By default, the libraries that don't explicitly specify SHARED/STATIC are build statically. # See https://cmake.org/cmake/help/latest/variable/BUILD_SHARED_LIBS.html @@ -211,7 +206,7 @@ endif (UNIX AND NOT MINGW) # (next two are relative to CMAKE_INSTALL_PREFIX) set(AI_LIBS_DIR "${DATADIR}" CACHE STRING "Where to install Skirmish AI libraries") set(AI_DATA_DIR "${AI_LIBS_DIR}" CACHE STRING "Where to install Skirmish AI additional files (eg. configuration)") -set(AI_TYPES "NATIVE" CACHE STRING "Which AI Interfaces (and Skirmish AIs using them) to build [ALL|NATIVE|JAVA|NONE]") +set(AI_TYPES "NATIVE" CACHE STRING "Which AI Interfaces (and Skirmish AIs using them) to build [ALL|NATIVE|NONE]") ## DataDirs set(BUILTIN_DATADIRS "") diff --git a/rts/build/cmake/UtilJava.cmake b/rts/build/cmake/UtilJava.cmake deleted file mode 100644 index 71874cc4966..00000000000 --- a/rts/build/cmake/UtilJava.cmake +++ /dev/null @@ -1,101 +0,0 @@ -# This file is part of the Spring engine (GPL v2 or later), see LICENSE.html - -# -# Spring Java related CMake utilities -# ----------------------------------- -# -# Variables set in this file: -# * JAVA_GLOBAL_LIBS_DIRS -# -# Functions and macros defined in this file: -# * find_java_lib -# * get_first_sub_dir_name -# * create_classpath -# * concat_classpaths -# * find_manifest_file -# - - -if (CMAKE_HOST_WIN32) - set(JAVA_GLOBAL_LIBS_DIRS "${MINGWLIBS}") -else (CMAKE_HOST_WIN32) - set(JAVA_GLOBAL_LIBS_DIRS "/usr/share/java" "/usr/local/share/java") -endif (CMAKE_HOST_WIN32) -make_global(JAVA_GLOBAL_LIBS_DIRS) - - -# Looks for a Java library (Jar file) in system wide search paths and in -# additional dirs supplied as argument. -macro (find_java_lib path_var libName additionalSearchDirs) - find_file(${path_var} "${libName}.jar" - PATHS ${additionalSearchDirs} ${JAVA_GLOBAL_LIBS_DIRS} - DOC "Path to the Java library ${libName}.jar" - NO_DEFAULT_PATH - NO_CMAKE_FIND_ROOT_PATH - ) - if (NOT ${path_var}) - message(SEND_ERROR "${libName}.jar not found!") - else (NOT ${path_var}) - ai_message(STATUS "Found ${libName}.jar: ${${path_var}}") - endif (NOT ${path_var}) -endmacro (find_java_lib path_var libName additionalSearchDirs) - - -# Returns the name of the first sub-dir (in alphabetical descending order) -# under dir. -macro (get_first_sub_dir_name name_var dir) - file(GLOB dirContent RELATIVE "${dir}" "${dir}/*") - foreach (dirPart ${dirContent}) - if (IS_DIRECTORY "${dir}/${dirPart}") - set(${name_var} ${dirPart}) - break() - endif (IS_DIRECTORY "${dir}/${dirPart}") - endforeach (dirPart) -endmacro (get_first_sub_dir_name name_var dir) - - -# Recursively lists all JAR files in a given directory -# and concatenates them in a Java Classpath compatible way into a single string. -macro (create_classpath classPath_var dir) - file(GLOB_RECURSE ${classPath_var} FOLLOW_SYMLINKS "${dir}/*.jar") - # Make sure we use the correct path delimiter for the compiling system - string(REPLACE ";" "${PATH_DELIM_H}" ${classPath_var} "${${classPath_var}}") -endmacro (create_classpath classPath_var dir) - - -# Concatenates an arbitrary number of Java ClassPaths (may be empty). -function (concat_classpaths resultingCP_var) - set(${resultingCP_var} "") - foreach (cpPart ${ARGN}) - set(${resultingCP_var} "${${resultingCP_var}}${cpPart}${PATH_DELIM_H}") - endforeach (cpPart) - string(REGEX REPLACE "${PATH_DELIM_H}\$" "" ${resultingCP_var} "${${resultingCP_var}}") - set(${resultingCP_var} "${${resultingCP_var}}" PARENT_SCOPE) -endfunction (concat_classpaths) - -# Look for a manifest.mf file in a few specific sub-dirs. -# This could be done with a simple find_file call, -# but that strangely does not find the file under win32, -# so we use this workaround -function (find_manifest_file srcDir result_var) - set(manifestSubdirs - "/src/main/resources/META-INF/" - "/src/" - "/") - set(${result_var} "${result_var}-NOTFOUND") - if (CMAKE_HOST_WIN32) - foreach(subDir_var ${manifestSubdirs}) - if (EXISTS "${srcDir}${subDir_var}manifest.mf") - set(${result_var} "${srcDir}${subDir_var}manifest.mf") - break() - endif (EXISTS "${srcDir}${subDir_var}manifest.mf") - endforeach(subDir_var) - else (CMAKE_HOST_WIN32) - find_file(${result_var} - NAMES "manifest.mf" "MANIFEST.MF" - PATHS "${srcDir}" - PATH_SUFFIXES ${manifestSubdirs} - NO_DEFAULT_PATH) - endif (CMAKE_HOST_WIN32) - set(${result_var} ${${result_var}} PARENT_SCOPE) -endfunction (find_manifest_file)