├── .github ├── database-square-tests.png ├── database-square.png ├── database-square.xcf ├── database-wide-short-lighthmode.png ├── database-wide-short.png ├── database-wide.png └── database-wide.xcf ├── .gitignore ├── ATTRIBUTIONS.txt ├── LICENSE.txt ├── README.md ├── docs ├── async-operations.md ├── connection-info.md ├── db-context.md ├── db-entity.md ├── drivers │ ├── binary-file.md │ ├── in-memory.md │ ├── index.md │ ├── json-file.md │ └── proxy-mongodb.md ├── index.md ├── query-builder.md ├── repositories.md └── result-ordering.md ├── src ├── EnfusionDatabaseFramework.gproj ├── Scripts │ ├── Game │ │ ├── ArmaReforgerScripted.c │ │ ├── Drivers │ │ │ ├── EDF_DbConnectionInfo.c │ │ │ ├── EDF_DbDriver.c │ │ │ ├── EDF_DbDriverBufferWrapper.c │ │ │ ├── InMemory │ │ │ │ ├── EDF_InMemoryDatabase.c │ │ │ │ └── EDF_InMemoryDbDriver.c │ │ │ ├── LocalFile │ │ │ │ ├── EDF_BinaryFileDbDriver.c │ │ │ │ ├── EDF_DbEntityCache.c │ │ │ │ ├── EDF_FileDbDriverBase.c │ │ │ │ └── EDF_JsonFileDbDriver.c │ │ │ └── WebProxy │ │ │ │ ├── EDF_MongoDbDriver.c │ │ │ │ └── EDF_WebProxyDbDriverBase.c │ │ ├── EDF_BufferedDbContext.c │ │ ├── EDF_Callback.c │ │ ├── EDF_DbContext.c │ │ ├── EDF_DbEntity.c │ │ ├── EDF_DbEntityIdGenerator.c │ │ ├── EDF_DbEntitySorter.c │ │ ├── EDF_DbEntityUtils.c │ │ ├── EDF_DbFindCondition.c │ │ ├── EDF_DbFindConditionEvaluator.c │ │ ├── EDF_DbOperationResultTypes.c │ │ ├── EDF_DbRepository.c │ │ ├── EDF_DbRepositoryFactory.c │ │ ├── EDF_DbRepositoryHelper.c │ │ ├── EDF_HexHelper.c │ │ ├── EDF_RefArrayCaster.c │ │ ├── EDF_ReflectionUtils.c │ │ ├── EDF_ScriptInvokerCallback.c │ │ └── SCR_BaseGameMode.c │ └── WorkbenchGame │ │ └── WorldEditor │ │ └── EDF_ClearDatabasePlugin.c └── thumbnail.png └── tests ├── EnfusionDatabaseFramework.Tests.gproj ├── Scripts └── Game │ ├── Drivers │ ├── EDF_DbDriverBufferWrapperTests.c │ ├── InMemory │ │ └── EDF_InMemoryDbDriverTests.c │ ├── LocalFile │ │ ├── EDF_BinaryFileDbDriverTests.c │ │ └── EDF_JsonFileDbDriverTests.c │ └── WebProxy │ │ └── EDF_WebProxyDbDriverTests.c │ ├── EDF_DbEntityRepositoryTests.c │ ├── EDF_DbEntitySorterTests.c │ ├── EDF_DbEntityUtilsTests.c │ ├── EDF_DbFindConditionBuilderTests.c │ ├── EDF_DbFindConditionEvaluatorTests.c │ ├── EDF_HexHelperTests.c │ └── Setup │ ├── EDF_AutoTestEntity.c │ └── EDF_TestResult.c ├── Worlds ├── Layers │ └── default.layer ├── TestWorld.ent └── TestWorld.ent.meta └── thumbnail.png /.github/database-square-tests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arkensor/EnfusionDatabaseFramework/20c0fbf251fae89446a61fa511649850c29c0b52/.github/database-square-tests.png -------------------------------------------------------------------------------- /.github/database-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arkensor/EnfusionDatabaseFramework/20c0fbf251fae89446a61fa511649850c29c0b52/.github/database-square.png -------------------------------------------------------------------------------- /.github/database-square.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arkensor/EnfusionDatabaseFramework/20c0fbf251fae89446a61fa511649850c29c0b52/.github/database-square.xcf -------------------------------------------------------------------------------- /.github/database-wide-short-lighthmode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arkensor/EnfusionDatabaseFramework/20c0fbf251fae89446a61fa511649850c29c0b52/.github/database-wide-short-lighthmode.png -------------------------------------------------------------------------------- /.github/database-wide-short.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arkensor/EnfusionDatabaseFramework/20c0fbf251fae89446a61fa511649850c29c0b52/.github/database-wide-short.png -------------------------------------------------------------------------------- /.github/database-wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arkensor/EnfusionDatabaseFramework/20c0fbf251fae89446a61fa511649850c29c0b52/.github/database-wide.png -------------------------------------------------------------------------------- /.github/database-wide.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arkensor/EnfusionDatabaseFramework/20c0fbf251fae89446a61fa511649850c29c0b52/.github/database-wide.xcf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rdb 2 | 3 | src/license.txt 4 | -------------------------------------------------------------------------------- /ATTRIBUTIONS.txt: -------------------------------------------------------------------------------- 1 | Database icons created by Freepik - Flaticon https://www.flaticon.com/free-icons/database -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Arkensor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Everon Life 5 | 6 | 7 | [![Releases](https://img.shields.io/github/v/release/Arkensor/EnfusionDatabaseFramework?style=flat-square)](https://github.com/Arkensor/EnfusionDatabaseFramework/releases) 8 | [![Arma Reforger Workshop](https://img.shields.io/badge/Workshop-5D6EA74A94173EDF-blue?style=flat-square)](https://reforger.armaplatform.com/workshop/5D6EA74A94173EDF) 9 | [![License MIT](https://img.shields.io/badge/License-MIT-green?style=flat-square)](https://opensource.org/licenses/MIT) 10 |
11 | 12 | # Enfusion Database Framework 13 | 14 | > **Warning** 15 | > This framework is still in **BETA**. Until version 1.0.0 there is no backward compatibility guarantee! Expect some bugs/performance issues and function signature updates until then. Feedback via [issue](https://github.com/Arkensor/EnfusionDatabaseFramework/issues) or [discussion](https://github.com/Arkensor/EnfusionDatabaseFramework/discussions) is welcome. 16 | 17 | **A database framework to connect the Enfusion engine with SQL-, document- and local file databases.** 18 | 19 | > **Note** 20 | > Are you trying to persist the entire world state and not just a few self scripted db entites? Consider using [EnfusionPersistenceFramework](https://github.com/Arkensor/EnfusionPersistenceFramework) which is built on top of this project. 21 | 22 | ## 🚀 Features 23 | - ✅ Easy to setup your DB entities with the provided base classes 24 | - ✅ Built-in [GUID](https://en.wikipedia.org/wiki/GUID) generation for DB entities to avoid expensive roundtrips to the database 25 | - ✅ Powerful query builder to find DB entities by complex conditions 26 | - ✅ Sync (blocking) and Async (non-blocking) APIs for adding/updating, finding, and removing DB entities 27 | - ✅ Pagination and result sorting by nested properties 28 | - ✅ Optional repository pattern for easy strong typed results and re-useable queries. 29 | - 🚧 Migrations between storage backends e.g. `JsonFile` <-> `Http:MySQL` 30 | 31 | ### Drivers 32 | - ✅ [`InMemory`](docs/drivers/in-memory.md) for unit testing purposes 33 | - ✅ [`JsonFile`](docs/drivers/json-file.md) local `.json` files for workbench development and small data volumes 34 | - ✅ [`BinaryFile`](docs/drivers/binary-file.md) local `.bin` files, same purpose as JSON but much smaller in filesize. 35 | - 🚧 `BIBackend` local/cloud synced `.bin` files stored in the Bohemia Interactive session backend. 36 | - 🚧 `Http` a web API proxy to other external storage services such as SQL and document databases. 37 | - ✅ Document Databases [`MongoDB`](docs/drivers/proxy-mongodb.md) 38 | - 🚧 SQL Databases `SQLite`, `MySQL`, `PostgreSQL` 39 | 40 | ## 📖 Documentation 41 | Detailed information on the individual classes and best practices can be found [here](docs/index.md). 42 | 43 | ## ⚡ Quick start 44 | ```cs 45 | [EDF_DbName.Automatic()] 46 | class TAG_MyPersistentInfo : EDF_DbEntity 47 | { 48 | float m_fNumber; 49 | string m_sText; 50 | 51 | //------------------------------------------------------------------------------------------------ 52 | //! Db entities can not have a constructor with parameters, this is a limitation of the engine. 53 | //! Consult the docs for more info on this. 54 | static TAG_MyPersistentInfo Create(float number, string text) 55 | { 56 | TAG_MyPersistentInfo instance(); 57 | instance.m_fNumber = number; 58 | instance.m_sText = text; 59 | return instance; 60 | } 61 | }; 62 | 63 | class EDF_QuickstartAction : ScriptedUserAction 64 | { 65 | //------------------------------------------------------------------------------------------------ 66 | override void PerformAction(IEntity pOwnerEntity, IEntity pUserEntity) 67 | { 68 | // Get the connection info as an attribute or parse it from CLI params etc. 69 | EDF_JsonFileDbConnectionInfo connectInfo(); 70 | connectInfo.m_sDatabaseName = "MyJsonDatabase"; 71 | 72 | // Get a db context instance and save it somewhere to re-use in e.g. a singleton 73 | EDF_DbContext dbContext = EDF_DbContext.Create(connectInfo); 74 | 75 | // For convenience interact with the DB context through a repository 76 | EDF_DbRepository repository = EDF_DbEntityHelper.GetRepository(dbContext); 77 | 78 | // Add some entries 79 | repository.AddOrUpdateAsync(TAG_MyPersistentInfo.Create(13.37, "Hello")); 80 | repository.AddOrUpdateAsync(TAG_MyPersistentInfo.Create(42.42, "World!")); 81 | 82 | // Now find hello 83 | EDF_DbFindCondition condition = EDF_DbFind.Field("m_sText").Contains("Hello"); 84 | EDF_DbFindCallbackSingle helloHandler(this, "FindHelloHandler"); 85 | repository.FindFirstAsync(condition, callback: helloHandler); 86 | } 87 | 88 | protected void FindHelloHandler(EDF_EDbOperationStatusCode statusCode, TAG_MyPersistentInfo result) 89 | { 90 | PrintFormat("FindHelloHandler invoked! - StatusCode: %1", 91 | typename.EnumToString(EDF_EDbOperationStatusCode, statusCode)); 92 | 93 | if (result) 94 | PrintFormat("Result: %1(id: %2, number: %3, text: %4)", 95 | result, result.GetId(), result.m_fNumber, result.m_sText) 96 | } 97 | }; 98 | ``` 99 | You should see this in your script console after executing the user action 100 | > FindHelloHandler invoked! - StatusCode: SUCCESS 101 | > Result: TAG_MyPersistentInfo<0x0000020D41100670>(id: 646e0c40-0000-0000-32cd-65805811b000, number: 13.37, text: Hello) 102 | 103 | And in your profile find these two files 104 | - `profile/.db/MyJsonDatabase/MyPersistentInfos/646e0c40-0000-0000-32cd-65805811b000.json` 105 | - `profile/.db/MyJsonDatabase/MyPersistentInfos/646e0c40-0000-0001-1a8d-352051eea400.json` 106 | 107 | And inside `646e0c40-0000-0000-32cd-65805811b000.json` 108 | ```json 109 | { 110 | "m_sId": "646e0c40-0000-0000-32cd-65805811b000", 111 | "m_fNumber": 13.36999, 112 | "m_sText": "Hello" 113 | } 114 | ``` 115 | -------------------------------------------------------------------------------- /docs/async-operations.md: -------------------------------------------------------------------------------- 1 | # Async operations 2 | Whenever possible all scripted logic should use async operations. This is because the sync API can and will lock up the server for the entire duration the storage backends needs for the data access. Even a few milliseconds of the server not being able to respond to packages and continue its simulation can result in undesirable gameplay effects such as rubberbanding, desync, or crashes. The sync API only makes sense for blocking load during game world init and blocking save during "mission exit" or host shutdown. For shutdown all async operations are force re-routed to be sync, otherwise, data might get lost. 3 | 4 | ## FindAllAsync callback classes 5 | If you want to process the result inside a separate callback class you can create one and implement the functions defined in the base class to get already strong typed results. 6 | Besides [`EDF_DbFindCallbackMultiple`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbOperationResultTypes.c;105) for multiple return values there is also [`EDF_DbFindCallbackSingle`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbOperationResultTypes.c;133) for expecting a single result as well as [`EDF_DbFindCallbackSingleton`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbOperationResultTypes.c;164) to create a result instance if nothing was found. 7 | ```cs 8 | class MyCustomDbEntityFindCallback : EDF_DbFindCallbackMultiple 9 | { 10 | //------------------------------------------------------------------------------------------------ 11 | override void OnSuccess(array results, Managed context) 12 | { 13 | results.Debug(); 14 | Print(context); 15 | } 16 | 17 | //------------------------------------------------------------------------------------------------ 18 | override void OnFailure(EDF_EDbOperationStatusCode statusCode, Managed context) 19 | { 20 | Print(statusCode); 21 | Print(context); 22 | } 23 | } 24 | ... 25 | void DoFind() 26 | { 27 | MyCustomDbEntityFindCallback myCallback(); 28 | dbContext.FindAllAsync(TAG_MyCustomDbEntity, callback: myCallback); 29 | } 30 | ``` 31 | 32 | ## FindAllAsync callback method 33 | As an alternative to a separate class, the result can be handled by a dedicated method. 34 | ```cs 35 | class MyDbLogic 36 | { 37 | //------------------------------------------------------------------------------------------------ 38 | void DoFind() 39 | { 40 | EDF_DbFindCallbackSingle myCallback(this, "HandleResult"); 41 | dbContext.FindAllAsync(TAG_MyCustomDbEntity, callback: myCallback); 42 | } 43 | 44 | //------------------------------------------------------------------------------------------------ 45 | protected void HandleResult(EDF_EDbOperationStatusCode code, TAG_MyCustomDbEntity result, Managed context) 46 | { 47 | Print(code); 48 | Print(result); 49 | Print(context); 50 | } 51 | } 52 | ``` 53 | 54 | ## Callback context 55 | In async operations, there is an optional `Managed` parameter called `context`. This allows for the caller scope to pass down additional "arguments" that are needed when processing the result. The value can be as simple as `TupleN` or a custom context type is used. The callback holds a strong reference to it, so any information in the context is kept alive until the callback completes. 56 | 57 | When not needed the context parameter can actually be omitted from a dedicated result handler method signature. 58 | E.g `protected void HandleResult(EDF_EDbOperationStatusCode code, TAG_MyCustomDbEntity result)` 59 | 60 | ```cs 61 | class MyContextDbLogic 62 | { 63 | //------------------------------------------------------------------------------------------------ 64 | void DoFind(string someParam) 65 | { 66 | Tuple1 context(someParam); 67 | EDF_DbFindCallbackSingle myCallback(this, "HandleResult", context); 68 | dbContext.FindAllAsync(TAG_MyCustomDbEntity, callback: myCallback); 69 | } 70 | 71 | //------------------------------------------------------------------------------------------------ 72 | protected void HandleResult(EDF_EDbOperationStatusCode code, TAG_MyCustomDbEntity result, Managed context) 73 | { 74 | Tuple1 typedContext = Tuple1.Cast(context); 75 | string someParam = typedContext.param1; // We get our DoFind parameter back yay! 76 | } 77 | } 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/connection-info.md: -------------------------------------------------------------------------------- 1 | # ConnectionInfo 2 | The connection info used by the [`EDF_DbContext`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbContext.c;1) / [`EDF_DbDriver`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/Drivers/EDF_DbDriver.c;1) is based on [`EDF_DbConnectionInfoBase`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/Drivers/EDF_DbConnectionInfo.c;2). 3 | 4 | Connection info objects can be created directly from script. 5 | ```cs 6 | EDF_JsonFileDbConnectionInfo connectInfo(); 7 | connectInfo.m_sDatabaseName = "MyDatabase"; 8 | connectInfo.m_bPrettify = true; 9 | ``` 10 | 11 | They can also be parsed from CLI params or other string sources using [`EDF_DbConnectionInfoBase::Parse()`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/Drivers/EDF_DbConnectionInfo.c;8). 12 | The format for this is `://?=&=`. 13 | ```cs 14 | // ArmaReforgerServer.exe "-ConnectionString=JsonFile://MyDatabase?prettify=true" 15 | protected EDF_DbConnectionInfoBase GetConnectionInfo() 16 | { 17 | string connectionString; 18 | if (!System.GetCLIParam("ConnectionString", connectionString)) 19 | return null; 20 | 21 | return EDF_DbConnectionInfoBase.Parse(connectionString); 22 | } 23 | ``` 24 | The available connection options can be found on the individual [driver](drivers/index.md) pages. 25 | -------------------------------------------------------------------------------- /docs/db-context.md: -------------------------------------------------------------------------------- 1 | # [`EDF_DbContext`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbContext.c;1) 2 | The `EDF_DbContext` API works with a base class [`EDF_DbEntity`](db-entity.md) that represents one distinct entity inside the database. The 3 | core operations performed through it are [`AddOrUpdate`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbContext.c;9), [`Remove`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbContext.c;21) and [`FindAll`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbContext.c;47). 4 | 5 | ## Constructing and managing your context 6 | To create a new context use [`EDF_DbContext.Create()`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbContext.c;101) and pass it the [connection info](connection-info.md) you want to use. It is recommended to re-use the instance for as long as possible to benefit from any under-the-hood optimizations that are added later such as caching and buffering. One possibility is to use a singleton for it. 7 | ```cs 8 | class TAG_MyDbContextSingleton 9 | { 10 | protected static ref EDF_DbContext s_pInstance; 11 | 12 | //------------------------------------------------------------------------------------------------ 13 | static EDF_DbContext GetInstance() 14 | { 15 | EDF_DbConnectionInfoBase connectionInfo = ...; // Get it from config or somewhere else. 16 | if (!s_pInstance) 17 | s_pInstance = EDF_DbContext.Create(connectionInfo); 18 | 19 | return s_pInstance; 20 | } 21 | }; 22 | 23 | EDF_DbContext context = TAG_MyDbContextSingleton.GetInstance(); 24 | ``` 25 | 26 | ## Using the context 27 | The DB context offers both a sync and [async (non-blocking)](async-operations.md) API for direct interactions. The usage is documented inline in the source code. 28 | For convenience, there is also the possibility of setting up a [repository](repositories.md) for your DB entity types. 29 | 30 | > **Note** 31 | > A general **important performance consideration** is to try and only do query by ID whenever possible if the project should be compatible with all possible storage backends. If it is only intended to be used together with e.g. `Http:MySQL` then query performance is not a concern of the scripted logic and is purely influenced by how the "real" database is configured. One way to optimize the usage for maximum compatibility is to try and remember relations by ID. Instead of querying all groups to see if a player is in it, store a group id on the player's save-data. On load, a condition can still be added to check if the player is actually still in the group and if it even exists. The performance diagnostics can aid in finding inefficient db interactions. 32 | 33 | Details for how to build queries can be found here [Query builder](query-builder.md). 34 | 35 | ## Simple FindAll example 36 | `FindAll` returns an `EDF_DbFindResults` instance which contains the status code enum as well as any results from the operation if it was successful. Depending on the use case, during development, when this simple example works, consider switching to [async operations](async-operations.md) as a best practice. 37 | ```cs 38 | EDF_DbFindResults result = dbContext.FindAll(...); 39 | 40 | //Something went wrong 41 | if (result.GetStatusCode() != EL_EDbOperationStatusCode.SUCCESS) 42 | return; 43 | 44 | //same as above, for convenience 45 | if (!result.Success()) 46 | return; 47 | 48 | array entities = results.GetEntities(); 49 | entities.Debug(); 50 | ... 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/db-entity.md: -------------------------------------------------------------------------------- 1 | # [`EDF_DbEntity`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbEntity.c;1) 2 | The [`EDF_DbContext`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbContext.c;1) API works with a base class `EDF_DbEntity` that represents one distinct entity inside the database. 3 | 4 | ## The Entity GUID 5 | Each `EDF_DbEntity` is identified with an [GUID](https://en.wikipedia.org/wiki/GUID) represented as 36 character hexadecimal string, that can be assigned manually via [`EDF_DbEntity::SetId()`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbEntity.c;16) or will be assigned by the [`EDF_DbContext::AddOrUpdate()`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbContext.c;9) variants. 6 | 7 | The GUID is generated in script by the [`EDF_DbEntityIdGenerator`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbEntityIdGenerator.c;1) to avoid any round trips to the database and allow for deterministic async processing of all requests. The GUID is always lowercase. 8 | 9 | | 646e0c40 | - | 0000 | - | 0005 | - | 32cd | - | 6580 | 5811b000 | 10 | |--------------|---|-----------|---|----------|---|---------------|---|--------------|----------| 11 | | TTTTTTTT | | SEQ1 | | SEQ1 | | RND1 | | RND1 | RND2RND2 | 12 | 13 | - `TTTTTTTT` UNIX UTC 32-bit date time stamp. Gives the ID general lexograpical sortability by creation time. Minimizes collisions as each ID becomes unique per second. 14 | - `SEQ1` gives guaranteed uniqueness to the ID due to the combination with the timestamp. Starts at 0 per session. Is incremented by one for each generated ID. There can be skips in the sequence number making their way to the database as not all generated ids will end up being used. 15 | - `RND1` and `RND2` aim to minimize predictability by malicious actors by introducing pseudo-random numbers. These are however **NOT** cryptographically safe! 16 | 17 | ## Creating your own database entity 18 | To create a database entity just inherit from the `EDF_DbEntity` base class. Optionally you can decorate the class with the [`[EDF_DbName("")]`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbEntity.c;43) attribute. It is used when the type is stored somewhere as a string e.g. JSON serialization. There is also [`[EDF_DbName.Automatic()]`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbEntity.c;73) which converts any class of the pattern `TAG_ActuallyRelevantPart` into just `ActuallyRelevantPart`. 19 | 20 | > **Warning** 21 | > Because of the serialization support, all DB entity classes MUST have 0 parameters in their constructor if a custom one is defined. This is a limitation of the deserialization of the engine. Instead, consider adding a static `Create` function. Inside, you can still access private or protected members using the code setup below. 22 | 23 | ```cs 24 | [EDF_DbName("MyCustomRenamedDbEntity")] 25 | class TAG_MyCustomDbEntity : EDF_DbEntity 26 | { 27 | int m_iValue; 28 | 29 | //------------------------------------------------------------------------------------------------ 30 | static TAG_MyCustomDbEntity Create(int value) 31 | { 32 | TAG_MyCustomDbEntity instance(); 33 | instance.m_iValue = value; 34 | return instance; 35 | } 36 | }; 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/drivers/binary-file.md: -------------------------------------------------------------------------------- 1 | # BinaryFile 2 | Local `.bin` files for workbench development and *small* data volumes. As compact as possible in terms of file size. **Do not use** for high-frequency complex queries on larger collections. Can however scale infinitely with the same speed on find by ID-only queries. Consult query performance metrics and if causing frame time problems consider switching to a "real" database via `Http` proxy. 3 | 4 | ### Implementation: [`EDF_BinaryFileDbDriver`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/Drivers/LocalFile/EDF_BinaryFileDbDriver.c;7) 5 | 6 | ### Aliases: `BinFile`, `Bin` 7 | 8 | ### ConnectionInfo: [`EDF_BinaryFileDbConnectionInfo`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/Drivers/LocalFile/EDF_BinaryFileDbDriver.c;2) 9 | | Option | Values | Description | 10 | |----------|------------|-----------------------------------------------------------------------------------| 11 | | Cache | True/False | Cache read results from disk to reduce file IO operations on consecutive queries. | 12 | -------------------------------------------------------------------------------- /docs/drivers/in-memory.md: -------------------------------------------------------------------------------- 1 | # InMemory 2 | Data is stored locally in game/workbench process memory. Built primarily for unit testing purposes. 3 | DB entities stored and returned are *deep copies*. So changing properties on inserted or retrieved instances is *safe*! 4 | 5 | ### Implementation: [`EDF_InMemoryDbDriver`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/Drivers/InMemory/EDF_InMemoryDbDriver.c;7) 6 | 7 | ### Aliases: None. 8 | 9 | ### ConnectionInfo: [`EDF_InMemoryDbConnectionInfo`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/Drivers/InMemory/EDF_InMemoryDbDriver.c;2) 10 | No options are available. 11 | -------------------------------------------------------------------------------- /docs/drivers/index.md: -------------------------------------------------------------------------------- 1 | # Drivers 2 | Full list of all currently available drivers 3 | - [InMemory](in-memory.md) 4 | - [JsonFile](json-file.md) 5 | - [BinaryFile](binary-file.md) 6 | - [Http:MongoDB](proxy-mongodb.md) 7 | 8 | > **Note** 9 | > Driver names when used in script may have aliases. These are listed on the individual driver documentation pages. 10 | > The driver names are case invariant. 11 | -------------------------------------------------------------------------------- /docs/drivers/json-file.md: -------------------------------------------------------------------------------- 1 | # JsonFile 2 | Local `.json` files for workbench development and *small* data volumes. **Do not use** for high-frequency complex queries on larger collections. Can however scale infinitely with the same speed on find by ID-only queries. Consult query performance metrics and if causing frame time problems consider switching to a "real" database via `Http` proxy. 3 | 4 | ### Implementation: [`EDF_JsonFileDbDriver`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/Drivers/LocalFile/EDF_JsonFileDbDriver.c;16) 5 | 6 | ### Aliases: `Json` 7 | 8 | ### ConnectionInfo: [`EDF_JsonFileDbConnectionInfo`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/Drivers/LocalFile/EDF_JsonFileDbDriver.c;2) 9 | | Option | Values | Description | 10 | |----------|------------|-----------------------------------------------------------------------------------| 11 | | Cache | True/False | Cache read results from disk to reduce file IO operations on consecutive queries. | 12 | | Prettify | True/False | Save files prettified (formatted). Primarily for debugging. | 13 | -------------------------------------------------------------------------------- /docs/drivers/proxy-mongodb.md: -------------------------------------------------------------------------------- 1 | # Http:MongoDB 2 | A [http api proxy](https://github.com/Arkensor/EnfusionDatabaseFramework.Drivers.WebProxy.MongoDB) based driver for the [MongoDB](https://mongodb.com) document database. Excellent storage for the highly unstructured data that results from the entity component system in Arma Reforger. 3 | 4 | > **Note** 5 | > Consider manually adding query indicies to your MongoDB server for collections that are often queried with complex conditions. This can signifantly reduce query time on large datasets. 6 | 7 | ### Implementation: [`EDF_MongoDbDriver`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/Drivers/WebProxy/EDF_MongoDbDriver.c;7) 8 | 9 | ### Aliases: `MongoDb`, `Mongo` 10 | 11 | ### ConnectionInfo: [`EDF_MongoDbConnectionInfo`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/Drivers/WebProxy/EDF_MongoDbDriver.c;2) 12 | Example: `MongoDb://MyDatabase?Host=1.2.3.4&Headers=user-agent,application/json,api-key,123456789&Parameters=key,value,key2,value2` 13 | 14 | | Option | Values | Description | 15 | |------------------|-------------|-----------------------------------------------------------| 16 | | ProxyHost | Hostname/IP | Web proxy hostname. | 17 | | ProxyPort | Portnumber | Web proxy port. | 18 | | SecureConnection | True/False | Use TLS/SSL to connect to the web proxy. | 19 | | Headers | key,value | Headers used for the request | 20 | | Parameters | key,value | Additional parameters added to the url with ...&key=value | 21 | 22 | > **Info** 23 | > If not specified the driver will automatically choose Content-Type "application/json" and User-Agent "AR-EDF". 24 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | Below individual topics are listed one should know about, to be able to use the database framework to its full potential. They are sorted by development steps. 3 | 4 | ### 1. [DBEntity](db-entity.md) 5 | How to set up db entities and how their GUID generation works. 6 | 7 | ### 2. [Drivers](drivers/index.md) 8 | Available storage backend drivers and their options. 9 | 10 | ### 3. [Connection info](connection-info.md) 11 | Different ways to construct the info needed to connect to the database. 12 | 13 | ### 4. [DBContext](db-context.md) 14 | The main entry point of database interactions. Introduction to the available script API with simple examples. 15 | 16 | ### 5. [Query builder](query-builder.md) 17 | How to build advanced queries to find entities by more than their GUID. 18 | 19 | ### 6. [Async operations](async-operations.md) 20 | The two ways of working with async operations and why they should be used whenever possible. 21 | 22 | ### 7. [Result ordering](result-ordering.md) 23 | Ordering results by nested properties 24 | 25 | ### 8. [Repository pattern](repositories.md) 26 | Benefits of using the repository pattern with examples of how to set one up. 27 | -------------------------------------------------------------------------------- /docs/query-builder.md: -------------------------------------------------------------------------------- 1 | # [`EDF_DbFind`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbFindCondition.c;1) 2 | [`EDF_DbEntity`](db-entity.md)s are queried from the [`EDF_DbContext`](db-context.md) using a [`EDF_DbFindCondition`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbFindCondition.c;29). These find conditions or "queries" are constructed through the `EDF_DbFind` builder. 3 | 4 | # Find by ID 5 | A simple find by ID can look like this: 6 | ```cs 7 | EDF_DbFindCondition condition = EDF_DbFind.Id().Equals("GUID-GOES-HERE"); 8 | EDF_DbFindResults results = dbContext.FindAll(, condition, limit: 1); 9 | ``` 10 | 11 | # Find by field 12 | Besides the `Id()` shortcut any other field can be queried using the `Field()` method on the builder. 13 | ```cs 14 | EDF_DbFindCondition condition = EDF_DbFind.Field("m_Name").Equals("Foo"); 15 | ``` 16 | The field name supports "dot-notation" so you can navigate complex types as well as collections using it. 17 | It is recommended to use the builder variant, instead of manually setting up the path to ensure future compatiblity. 18 | - `EDF_DbFind.Field("fieldName").Field("subField")` / `EDF_DbFind.Field("fieldName.subField")` 19 | - `EDF_DbFind.Field("floatArray").At(0)` / `EDF_DbFind.Field("floatArray.0")` evaluates the first element of the collection 20 | - `EDF_DbFind.Field("floatArray").At({0, 1})` / `EDF_DbFind.Field("floatArray.{0, 1}")` evaluates the first and second element of the collection 21 | - `EDF_DbFind.Field("classArray").OfType(FilteredType)` / `EDF_DbFind.Field("classArray.FilteredType")` evaluate collection collection elements of a complex type that inherit from the provided typename 22 | - `EDF_DbFind.Field("classArray").OfTypes({FilteredType, AnotherType})` / `EDF_DbFind.Field("classArray.{FilteredType, AnotherType}")` evaluate collection collection elements of a complex type that inherit from the provided typenames 23 | 24 | ## Field condition builder 25 | Summary of the available filed condition builder functions (`set` counts as `array`): 26 | | Function | Used on | Description | 27 | |---------------------|------------------|----------------------------------------------------------| 28 | | Not | * | Inverts the next statement | 29 | | NullOrDefault | * | Field is null, an empty array or primitive value default | 30 | | Equals | * | Full equality check | 31 | | EqualsAnyOf | Primitive | Equality check comparison against multiple values | 32 | | LessThan | Numeric | `<` operator | 33 | | LessThanOrEquals | Numeric | `<=` operator | 34 | | GreaterThan | Numeric | `>` operator | 35 | | GreaterThanOrEquals | Numeric | `>=` operator | 36 | | Between | Numeric | LOWER `<` VALUE `<` UPPER | 37 | | Invariant | String | Makes the condition case-insensitive | 38 | | Length | String | Length of string | 39 | | Count | Array/Map | Number of elements inside the collection | 40 | | Contains | String/Array/Map | Contains the value | 41 | | ContainsAnyOf | String/Array/Map | Contains at least one of the values | 42 | | ContainsAllOf | String/Array/Map | Contains at least all the values | 43 | | Any | Array | True if any array item matches the condition | 44 | | All | Array | True if all array items match the condition | 45 | | At | Array | Get array item at index | 46 | | OfType | Array | Evaluates array items that match the type | 47 | | Keys | Map | Get the key array of a map | 48 | | Values | Map | Get the value array of a map | 49 | 50 | To combine multiple conditions together you can use `And()` and `Or()` to build like so 51 | ```cs 52 | EDF_DbFind.And({ 53 | condition1, 54 | condition2, 55 | conditionN 56 | }); 57 | ``` 58 | 59 | Combining all of this together provides various ways to write precise database queries: 60 | ```cs 61 | EDF_DbFindCondition condition = EDF_DbFind.Or({ 62 | EDF_DbFind.Field("A").Not().Null(), 63 | EDF_DbFind.Field("B").Empty(), 64 | EDF_DbFind.And({ 65 | EDF_DbFind.Field("CString").Contains("SubString"), 66 | EDF_DbFind.Field("DBoolArray").Equals({true, false, true, true}), 67 | EDF_DbFind.And({ 68 | EDF_DbFind.Field("E.m_Numbers").Contains(100), 69 | EDF_DbFind.Field("F.m_ComplexWrapperSet").OfType(SomeType).Any().Field("someNumber").Not().EqualsAnyOf({1, 2}) 70 | }), 71 | EDF_DbFind.Or({ 72 | EDF_DbFind.Field("G").EqualsAnyOf({12, 13}) 73 | }) 74 | }) 75 | }); 76 | ``` 77 | > **Note** 78 | > If you get an error like "formula too complex" you need to split up your conditions into a few sub-conditions and join them together separately, as the script VM has some limits on how nested the builder can be. 79 | 80 | ### Query cheat sheet 81 | 82 | #### Collection only contains certain values: 83 | ```cs 84 | EDF_DbFind.Field("Collection").All().EqualsAnyOf({a, b, c}) 85 | ``` 86 | 87 | #### Collection unordered equality: 88 | ```cs 89 | EDF_DbFind.And({ 90 | EDF_DbFind.Field("Collection").ContainsAllOf({a, b, c}), 91 | DF_DbFind.Field("Collection").Count().Equals(3) 92 | }) 93 | ``` 94 | 95 | ### Polymorphism limitation 96 | Right now it is required to pass in the entire inheritance tree to match during an `OfType()` filtering. In later versions once https://feedback.bistudio.com/T172647 is implemented, it should be possible to just pass a base class and still match all inherited types. 97 | Given the classes `BaseType`, `InheritedTypeA : BaseType` and `InheritedTypeB : BaseType` a filter to match all of these needs to look like this currently: 98 | ```cs 99 | EDF_DbFind.Field("someCollection").OfTypes({BaseType, InheritedTypeA, InheritedTypeB}) 100 | ``` 101 | and later should only become 102 | ```cs 103 | EDF_DbFind.Field("someCollection").OfType(BaseType) 104 | ``` 105 | 106 | ### Performance considerations 107 | If possible make the condition a constant class member, that way it is created only once. For this to be possible all search values must be known constants as well. 108 | You can also cache more complex conditions as class members if you build them dynamically once and re-use them after. 109 | ```cs 110 | static const ref EDF_DbFindCondition s_ConstNameEqualsFoo = EDF_DbFind.Field("m_Name").Equals("Foo"); 111 | 112 | void MyFunction() 113 | { 114 | // Do something with s_ConstNameEqualsFoo 115 | ... 116 | } 117 | ``` 118 | -------------------------------------------------------------------------------- /docs/repositories.md: -------------------------------------------------------------------------------- 1 | # Repositories 2 | To make the handling of database entities easier the framework comes with a utility wrapper class called [`EDF_DbRepository`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbRepository.c;23). 3 | It contains a few commonly used methods such as `Find()` by id, `FindSingleton()`, `FindFirst()` by condition, as well as providing already casted results of the target entity type. 4 | All DB entities can be handled automatically through the default repository implementation. To get a repository for an entity there is a utility class: 5 | ```cs 6 | EDF_DbRepository repository = EDF_DbEntityHelper.GetRepository(dbContext); 7 | ``` 8 | 9 | ## Custom repositories 10 | Adding a customized implementation of a repository is also possible. In it, frequently used DB operations can be stored as re-useable methods. 11 | > **Note** 12 | > The [`EDF_DbRepositoryRegistration`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbRepository.c;136) attribute is a temporary requirement to make the repository known the framework ahead of any manual instantiation. This attribute will be deprecated in the future when there are better ways to get all repositories. 13 | ```cs 14 | [EDF_DbRepositoryRegistration()] 15 | class TAG_MyCustomDbEntityRepository : EDF_DbRepository 16 | { 17 | EDF_DbFindResultSingle FindByIntValue(int value) 18 | { 19 | return FindFirst(EDF_DbFind.Field("m_iIntValue").Equals(value)); 20 | } 21 | }; 22 | ``` 23 | 24 | An instance of this repository will be returned by the [`EDF_DbEntityHelper::GetRepository()`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbRepositoryHelper.c;7) method, however to also strong cast the resulting repository, a different utility class [`EDF_DbRepositoryHelper`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbRepositoryHelper.c;20) can be used. 25 | ```cs 26 | TAG_MyCustomDbEntityRepository repository = EDF_DbRepositoryHelper.Get(dbContext); 27 | ``` 28 | 29 | Manually getting/creating a repository instance is possible through the [`EDF_DbRepositoryFactory::GetRepository()`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbRepositoryFactory.c;6) function. The factory has the advantage of returning cached instances of the repository on the same db context. 30 | -------------------------------------------------------------------------------- /docs/result-ordering.md: -------------------------------------------------------------------------------- 1 | # Result ordering 2 | If multiple results are returned by the operation they can be automatically ordered by field values using "dot-notation" to navigate nested properties. 3 | The sorting order is given via a nested array as the `orderBy` parameter, with the second, third, ... n-th element only being used if the ordering based on the previous element left two or more entities with the same sorting result. 4 | Options for the sort direction are `ASC`, `DESC` as invariant strings or the [`EDF_EDbEntitySortDirection`](https://enfusionengine.com/api/redirect?to=enfusion://ScriptEditor/Scripts/Game/EDF_DbEntitySorter.c;1) "enum". 5 | ```cs 6 | dbContext.FindAll(..., orderBy: {{"child.subField", "ASC"}, {"thenByField", EDF_EDbEntitySortDirection.DESCENDING}}); 7 | ``` 8 | 9 | > **Note** 10 | > It is not possible to sort nested properties that are collections (array/set/map). Only primitive fields can be the sort value. 11 | -------------------------------------------------------------------------------- /src/EnfusionDatabaseFramework.gproj: -------------------------------------------------------------------------------- 1 | GameProject { 2 | ID "EnfusionDatabaseFramework" 3 | GUID "5D6EA74A94173EDF" 4 | TITLE "Enfusion Database Framework" 5 | Dependencies { 6 | "58D0FB3206B6F859" 7 | } 8 | } -------------------------------------------------------------------------------- /src/Scripts/Game/ArmaReforgerScripted.c: -------------------------------------------------------------------------------- 1 | modded class ArmaReforgerScripted 2 | { 3 | //------------------------------------------------------------------------------------------------ 4 | override void OnAfterInit(BaseWorld world) 5 | { 6 | EDF_DbRepositoryRegistration.FlushRegistrations(GetScriptModule()); 7 | super.OnAfterInit(world); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/Scripts/Game/Drivers/EDF_DbConnectionInfo.c: -------------------------------------------------------------------------------- 1 | [BaseContainerProps()] 2 | class EDF_DbConnectionInfoBase 3 | { 4 | [Attribute()] 5 | string m_sDatabaseName; 6 | 7 | //------------------------------------------------------------------------------------------------ 8 | void ReadOptions(string connectionString) 9 | { 10 | int until = connectionString.IndexOf("?"); 11 | if (until == -1) 12 | until = connectionString.Length(); 13 | 14 | m_sDatabaseName = connectionString.Substring(0, until); 15 | } 16 | 17 | //------------------------------------------------------------------------------------------------ 18 | static EDF_DbConnectionInfoBase Parse(string connectionString) 19 | { 20 | int driverEndIdx = connectionString.IndexOf("://"); 21 | if (driverEndIdx == -1) 22 | { 23 | Debug.Error(string.Format("Invalid -ConnectionString=... parameter value '%1'.", connectionString)); 24 | return null; 25 | } 26 | 27 | string driverName = connectionString.Substring(0, driverEndIdx); 28 | string connectionInfoString = connectionString.Substring(driverEndIdx + 3, connectionString.Length() - (driverName.Length() + 3)); 29 | 30 | typename driverType = EDF_DbDriverRegistry.Get(driverName); 31 | if (!driverType.IsInherited(EDF_DbDriver)) 32 | { 33 | Debug.Error(string.Format("Incompatible database driver type '%1'.", driverType)); 34 | return null; 35 | } 36 | 37 | typename connectionInfoType = EDF_DbConnectionInfoDriverType.GetConnectionInfoType(driverType); 38 | EDF_DbConnectionInfoBase connectionInfo = EDF_DbConnectionInfoBase.Cast(connectionInfoType.Spawn()); 39 | if (!connectionInfo) 40 | return null; 41 | 42 | connectionInfo.ReadOptions(connectionInfoString); 43 | 44 | return connectionInfo; 45 | } 46 | }; 47 | 48 | class EDF_DbConnectionInfoDriverType 49 | { 50 | protected static ref map s_mMapping; 51 | protected static ref map s_mReverseMapping; 52 | 53 | //------------------------------------------------------------------------------------------------ 54 | static typename GetDriverType(typename connectionInfoType) 55 | { 56 | if (s_mMapping) 57 | return s_mMapping.Get(connectionInfoType); 58 | 59 | return typename.Empty; 60 | } 61 | 62 | //------------------------------------------------------------------------------------------------ 63 | static typename GetConnectionInfoType(typename driverType) 64 | { 65 | if (s_mReverseMapping) 66 | return s_mReverseMapping.Get(driverType); 67 | 68 | return typename.Empty; 69 | } 70 | 71 | //------------------------------------------------------------------------------------------------ 72 | void EDF_DbConnectionInfoDriverType(typename driver) 73 | { 74 | typename connectionInfoType = EDF_ReflectionUtils.GetAttributeParent(); 75 | 76 | if (!s_mMapping) 77 | s_mMapping = new map(); 78 | 79 | if (!s_mReverseMapping) 80 | s_mReverseMapping = new map(); 81 | 82 | s_mMapping.Set(connectionInfoType, driver); 83 | s_mReverseMapping.Set(driver, connectionInfoType); 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /src/Scripts/Game/Drivers/EDF_DbDriver.c: -------------------------------------------------------------------------------- 1 | class EDF_DbDriver 2 | { 3 | static bool s_bForceBlocking; //Used to forced drivers to rely on sync api during session teardown 4 | 5 | //------------------------------------------------------------------------------------------------ 6 | bool Initialize(notnull EDF_DbConnectionInfoBase connectionInfo); 7 | 8 | //------------------------------------------------------------------------------------------------ 9 | EDF_EDbOperationStatusCode AddOrUpdate(notnull EDF_DbEntity entity); 10 | 11 | //------------------------------------------------------------------------------------------------ 12 | EDF_EDbOperationStatusCode Remove(typename entityType, string entityId); 13 | 14 | //------------------------------------------------------------------------------------------------ 15 | EDF_DbFindResultMultiple FindAll(typename entityType, EDF_DbFindCondition condition = null, array orderBy = null, int limit = -1, int offset = -1); 16 | 17 | //------------------------------------------------------------------------------------------------ 18 | void AddOrUpdateAsync(notnull EDF_DbEntity entity, EDF_DbOperationStatusOnlyCallback callback = null); 19 | 20 | //------------------------------------------------------------------------------------------------ 21 | void RemoveAsync(typename entityType, string entityId, EDF_DbOperationStatusOnlyCallback callback = null); 22 | 23 | //------------------------------------------------------------------------------------------------ 24 | void FindAllAsync(typename entityType, EDF_DbFindCondition condition = null, array orderBy = null, int limit = -1, int offset = -1, EDF_DbFindCallbackBase callback = null); 25 | }; 26 | 27 | class EDF_DbDriverName 28 | { 29 | //------------------------------------------------------------------------------------------------ 30 | void EDF_DbDriverName(TStringArray driverAliases = null) 31 | { 32 | typename driverType = EDF_ReflectionUtils.GetAttributeParent(); 33 | 34 | EDF_DbDriverRegistry.Register(driverType.ToString(), driverType); 35 | 36 | if (driverAliases) 37 | { 38 | foreach (string alias : driverAliases) 39 | { 40 | EDF_DbDriverRegistry.Register(alias, driverType); 41 | } 42 | } 43 | } 44 | }; 45 | 46 | class EDF_DbDriverRegistry 47 | { 48 | protected static ref map s_mDrivers; 49 | 50 | //------------------------------------------------------------------------------------------------ 51 | static void Register(string driverName, typename driverType) 52 | { 53 | if (!s_mDrivers) 54 | s_mDrivers = new map(); 55 | 56 | string driverNameInvariant = driverName; 57 | driverNameInvariant.ToLower(); 58 | 59 | s_mDrivers.Set(driverNameInvariant, driverType); 60 | } 61 | 62 | //------------------------------------------------------------------------------------------------ 63 | static void Unregister(string driverName) 64 | { 65 | if (!s_mDrivers) 66 | return; 67 | 68 | string driverNameInvariant = driverName; 69 | driverNameInvariant.ToLower(); 70 | 71 | s_mDrivers.Remove(driverNameInvariant); 72 | } 73 | 74 | //------------------------------------------------------------------------------------------------ 75 | static typename Get(string driverName) 76 | { 77 | string driverNameInvariant = driverName; 78 | driverNameInvariant.ToLower(); 79 | 80 | if (!s_mDrivers || !s_mDrivers.Contains(driverNameInvariant)) 81 | return typename.Empty; 82 | 83 | return s_mDrivers.Get(driverNameInvariant); 84 | } 85 | 86 | //------------------------------------------------------------------------------------------------ 87 | static set GetAll() 88 | { 89 | set result(); 90 | 91 | if (s_mDrivers) 92 | { 93 | for (int nElement = 0; nElement < s_mDrivers.Count(); nElement++) 94 | { 95 | result.Insert(s_mDrivers.GetElement(nElement)); 96 | } 97 | } 98 | 99 | return result; 100 | } 101 | 102 | //------------------------------------------------------------------------------------------------ 103 | static void Reset() 104 | { 105 | delete s_mDrivers; 106 | } 107 | }; 108 | -------------------------------------------------------------------------------- /src/Scripts/Game/Drivers/InMemory/EDF_InMemoryDatabase.c: -------------------------------------------------------------------------------- 1 | typedef map EDF_InMemoryDatabaseTable; 2 | 3 | class EDF_InMemoryDatabase 4 | { 5 | string m_DbName; 6 | ref map m_EntityTables; 7 | 8 | //------------------------------------------------------------------------------------------------ 9 | void AddOrUpdate(notnull EDF_DbEntity entity) 10 | { 11 | EDF_InMemoryDatabaseTable table = GetTable(entity.Type()); 12 | 13 | if (table) 14 | table.Set(entity.GetId(), entity); 15 | } 16 | 17 | //------------------------------------------------------------------------------------------------ 18 | void Remove(typename entityType, string entityId) 19 | { 20 | EDF_InMemoryDatabaseTable table = GetTable(entityType); 21 | if (table) 22 | table.Remove(entityId); 23 | } 24 | 25 | //------------------------------------------------------------------------------------------------ 26 | EDF_DbEntity Get(typename entityType, string entityId) 27 | { 28 | EDF_InMemoryDatabaseTable table = GetTable(entityType); 29 | if (table) 30 | return table.Get(entityId); 31 | 32 | return null; 33 | } 34 | 35 | //------------------------------------------------------------------------------------------------ 36 | array GetAll(typename entityType) 37 | { 38 | EDF_InMemoryDatabaseTable table = GetTable(entityType); 39 | 40 | array result(); 41 | 42 | if (table) 43 | { 44 | for (int nElement = 0; nElement < table.Count(); nElement++) 45 | { 46 | result.Insert(table.GetElement(nElement)); 47 | } 48 | } 49 | 50 | return result; 51 | } 52 | 53 | //------------------------------------------------------------------------------------------------ 54 | protected EDF_InMemoryDatabaseTable GetTable(typename entityType) 55 | { 56 | string typeKey = entityType.ToString(); 57 | 58 | EDF_InMemoryDatabaseTable table = m_EntityTables.Get(typeKey); 59 | 60 | if (!table) 61 | { 62 | table = new EDF_InMemoryDatabaseTable(); 63 | m_EntityTables.Set(typeKey, table); 64 | } 65 | 66 | return table; 67 | } 68 | 69 | //------------------------------------------------------------------------------------------------ 70 | void EDF_InMemoryDatabase(string dbName) 71 | { 72 | m_DbName = dbName; 73 | m_EntityTables = new map(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Scripts/Game/Drivers/InMemory/EDF_InMemoryDbDriver.c: -------------------------------------------------------------------------------- 1 | [EDF_DbConnectionInfoDriverType(EDF_InMemoryDbDriver), BaseContainerProps()] 2 | class EDF_InMemoryDbConnectionInfo : EDF_DbConnectionInfoBase 3 | { 4 | }; 5 | 6 | [EDF_DbDriverName({"InMemory"})] 7 | class EDF_InMemoryDbDriver : EDF_DbDriver 8 | { 9 | protected static ref map s_mDatabases; 10 | protected EDF_InMemoryDatabase m_pDb; 11 | 12 | //------------------------------------------------------------------------------------------------ 13 | override bool Initialize(notnull EDF_DbConnectionInfoBase connectionInfo) 14 | { 15 | // Only create the db holder if at least one driver is initialized. (Avoids allocation on clients) 16 | if (!s_mDatabases) 17 | s_mDatabases = new map(); 18 | 19 | string dbName = connectionInfo.m_sDatabaseName; 20 | m_pDb = s_mDatabases.Get(dbName); 21 | 22 | // Init db if driver was the first one to trying to access it 23 | if (!m_pDb) 24 | { 25 | s_mDatabases.Set(dbName, new EDF_InMemoryDatabase(dbName)); 26 | m_pDb = s_mDatabases.Get(dbName); // Strong ref held by map so get it there 27 | } 28 | 29 | return true; 30 | } 31 | 32 | //------------------------------------------------------------------------------------------------ 33 | override EDF_EDbOperationStatusCode AddOrUpdate(notnull EDF_DbEntity entity) 34 | { 35 | if (!entity.HasId()) 36 | return EDF_EDbOperationStatusCode.FAILURE_ID_NOT_SET; 37 | 38 | // Make a copy so after insert you can not accidently change anything on the instance passed into the driver later. 39 | EDF_DbEntity deepCopy = EDF_DbEntity.Cast(EDF_DbEntityUtils.DeepCopy(entity)); 40 | 41 | m_pDb.AddOrUpdate(deepCopy); 42 | return EDF_EDbOperationStatusCode.SUCCESS; 43 | } 44 | 45 | //------------------------------------------------------------------------------------------------ 46 | override EDF_EDbOperationStatusCode Remove(typename entityType, string entityId) 47 | { 48 | if (!entityId) 49 | return EDF_EDbOperationStatusCode.FAILURE_ID_NOT_SET; 50 | 51 | if (!m_pDb.Get(entityType, entityId)) 52 | return EDF_EDbOperationStatusCode.FAILURE_ID_NOT_FOUND; 53 | 54 | m_pDb.Remove(entityType, entityId); 55 | return EDF_EDbOperationStatusCode.SUCCESS; 56 | } 57 | 58 | //------------------------------------------------------------------------------------------------ 59 | override EDF_DbFindResultMultiple FindAll(typename entityType, EDF_DbFindCondition condition = null, array orderBy = null, int limit = -1, int offset = -1) 60 | { 61 | array entities; 62 | 63 | // See if we can only load selected few entities by id or we need the entire collection to search through 64 | set loadIds(), skipIds(); 65 | bool needsFilter = false; 66 | if (EDF_DbFindConditionEvaluator.CollectConditionIds(condition, loadIds, skipIds) && 67 | !loadIds.IsEmpty() && // There must be something to load explictly 68 | skipIds.IsEmpty()) // and no "load xxx but skip these" 69 | { 70 | entities = {}; 71 | foreach (string relevantId : loadIds) 72 | { 73 | EDF_DbEntity entity = m_pDb.Get(entityType, relevantId); 74 | if (entity) 75 | entities.Insert(entity); 76 | } 77 | } 78 | else 79 | { 80 | entities = m_pDb.GetAll(entityType); 81 | needsFilter = true; 82 | } 83 | 84 | if (needsFilter && condition) 85 | entities = EDF_DbFindConditionEvaluator.GetFiltered(entities, condition); 86 | 87 | if (orderBy) 88 | entities = EDF_DbEntitySorter.GetSorted(entities, orderBy); 89 | 90 | array resultEntites(); 91 | 92 | foreach (int idx, EDF_DbEntity entity : entities) 93 | { 94 | // Respect output limit is specified 95 | if (limit != -1 && resultEntites.Count() >= limit) 96 | break; 97 | 98 | // Skip the first n records if offset specified (for paginated loading together with limit) 99 | if (offset != -1 && idx < offset) 100 | continue; 101 | 102 | // Return a deep copy so you can not accidentially change the db reference instance in the result handling code 103 | EDF_DbEntity deepCopy = EDF_DbEntity.Cast(EDF_DbEntityUtils.DeepCopy(entity)); 104 | resultEntites.Insert(deepCopy); 105 | } 106 | 107 | return new EDF_DbFindResultMultiple(EDF_EDbOperationStatusCode.SUCCESS, resultEntites); 108 | } 109 | 110 | //------------------------------------------------------------------------------------------------ 111 | override void AddOrUpdateAsync(notnull EDF_DbEntity entity, EDF_DbOperationStatusOnlyCallback callback = null) 112 | { 113 | // In memory is blocking, re-use sync api 114 | EDF_EDbOperationStatusCode statusCode = AddOrUpdate(entity); 115 | if (callback) 116 | callback.Invoke(statusCode); 117 | } 118 | 119 | //------------------------------------------------------------------------------------------------ 120 | override void RemoveAsync(typename entityType, string entityId, EDF_DbOperationStatusOnlyCallback callback = null) 121 | { 122 | // In memory is blocking, re-use sync api 123 | EDF_EDbOperationStatusCode statusCode = Remove(entityType, entityId); 124 | if (callback) 125 | callback.Invoke(statusCode); 126 | } 127 | 128 | //------------------------------------------------------------------------------------------------ 129 | override void FindAllAsync(typename entityType, EDF_DbFindCondition condition = null, array orderBy = null, int limit = -1, int offset = -1, EDF_DbFindCallbackBase callback = null) 130 | { 131 | // In memory is blocking, re-use sync api 132 | EDF_DbFindResultMultiple findResults = FindAll(entityType, condition, orderBy, limit, offset); 133 | if (callback) 134 | callback.Invoke(findResults.GetStatusCode(), findResults.GetEntities()); 135 | } 136 | 137 | //------------------------------------------------------------------------------------------------ 138 | void ~EDF_InMemoryDbDriver() 139 | { 140 | if (s_mDatabases && m_pDb) 141 | s_mDatabases.Remove(m_pDb.m_DbName); 142 | } 143 | }; 144 | -------------------------------------------------------------------------------- /src/Scripts/Game/Drivers/LocalFile/EDF_BinaryFileDbDriver.c: -------------------------------------------------------------------------------- 1 | [EDF_DbConnectionInfoDriverType(EDF_BinaryFileDbDriver), BaseContainerProps()] 2 | class EDF_BinaryFileDbConnectionInfo : EDF_FileDbDriverInfoBase 3 | { 4 | }; 5 | 6 | [EDF_DbDriverName({"BinaryFile", "BinFile", "Bin"})] 7 | class EDF_BinaryFileDbDriver : EDF_FileDbDriverBase 8 | { 9 | //------------------------------------------------------------------------------------------------ 10 | override protected string GetFileExtension() 11 | { 12 | return ".bin"; 13 | } 14 | 15 | //------------------------------------------------------------------------------------------------ 16 | override protected EDF_EDbOperationStatusCode WriteToDisk(EDF_DbEntity entity) 17 | { 18 | SCR_BinSaveContext writer(); 19 | if (!writer.WriteValue("", entity)) 20 | return EDF_EDbOperationStatusCode.FAILURE_DATA_MALFORMED; 21 | 22 | if (!writer.SaveToFile(string.Format("%1/%2.bin", _GetTypeDirectory(entity.Type()), entity.GetId()))) 23 | return EDF_EDbOperationStatusCode.FAILURE_DB_UNAVAILABLE; 24 | 25 | return EDF_EDbOperationStatusCode.SUCCESS; 26 | } 27 | 28 | //------------------------------------------------------------------------------------------------ 29 | override protected EDF_EDbOperationStatusCode ReadFromDisk(typename entityType, string entityId, out EDF_DbEntity entity) 30 | { 31 | string file = string.Format("%1/%2.bin", _GetTypeDirectory(entityType), entityId); 32 | if (FileIO.FileExists(file)) 33 | { 34 | SCR_BinLoadContext reader(); 35 | if (!reader.LoadFromFile(file)) 36 | return EDF_EDbOperationStatusCode.FAILURE_DB_UNAVAILABLE; 37 | 38 | entity = EDF_DbEntity.Cast(entityType.Spawn()); 39 | if (!reader.ReadValue("", entity)) 40 | return EDF_EDbOperationStatusCode.FAILURE_DATA_MALFORMED; 41 | } 42 | 43 | return EDF_EDbOperationStatusCode.SUCCESS; 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/Scripts/Game/Drivers/LocalFile/EDF_DbEntityCache.c: -------------------------------------------------------------------------------- 1 | class EDF_DbEntityCache 2 | { 3 | ref map m_EntityInstances; 4 | 5 | // TODO sorted linked list with last queried for, drop by id from cache if not asked for in X time 6 | 7 | //------------------------------------------------------------------------------------------------ 8 | void Add(EDF_DbEntity entity) 9 | { 10 | m_EntityInstances.Set(entity.GetId(), entity); 11 | } 12 | 13 | //------------------------------------------------------------------------------------------------ 14 | void Remove(string entityId) 15 | { 16 | m_EntityInstances.Remove(entityId); 17 | } 18 | 19 | 20 | EDF_DbEntity Get(string entityId) 21 | { 22 | return m_EntityInstances.Get(entityId); 23 | } 24 | 25 | //------------------------------------------------------------------------------------------------ 26 | void EDF_DbEntityCache() 27 | { 28 | m_EntityInstances = new map(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Scripts/Game/Drivers/LocalFile/EDF_FileDbDriverBase.c: -------------------------------------------------------------------------------- 1 | [EDF_DbConnectionInfoDriverType(EDF_JsonFileDbDriver), BaseContainerProps()] 2 | class EDF_FileDbDriverInfoBase : EDF_DbConnectionInfoBase 3 | { 4 | [Attribute(defvalue: "1", desc: "Cache read results from disk to reduce file IO operations on consecutive queries.")] 5 | bool m_bUseCache; 6 | 7 | //------------------------------------------------------------------------------------------------ 8 | override void ReadOptions(string connectionString) 9 | { 10 | super.ReadOptions(connectionString); 11 | connectionString.ToLower(); 12 | connectionString.Replace(" = ", "="); 13 | m_bUseCache = connectionString.Contains("cache=true"); 14 | } 15 | }; 16 | 17 | class EDF_FileDbDriverBase : EDF_DbDriver 18 | { 19 | const string DB_BASE_DIR = "$profile:/.db"; //Can be changed through modded class if you want to! 20 | 21 | // Static props that can be shared across all driver instances 22 | protected ref EDF_DbEntityCache m_pEntityCache; 23 | protected ref map> m_mEntityIdsyCache; 24 | 25 | protected string m_sDbDir; 26 | protected bool m_bUseCache; 27 | 28 | //------------------------------------------------------------------------------------------------ 29 | override bool Initialize(notnull EDF_DbConnectionInfoBase connectionInfo) 30 | { 31 | if (!m_pEntityCache) 32 | m_pEntityCache = new EDF_DbEntityCache(); 33 | 34 | if (!m_mEntityIdsyCache) 35 | m_mEntityIdsyCache = new map>(); 36 | 37 | auto fileConnectInfo = EDF_FileDbDriverInfoBase.Cast(connectionInfo); 38 | m_bUseCache = fileConnectInfo.m_bUseCache; 39 | 40 | string dbName = fileConnectInfo.m_sDatabaseName; 41 | m_sDbDir = string.Format("%1/%2", DB_BASE_DIR, dbName); 42 | FileIO.MakeDirectory(DB_BASE_DIR); 43 | FileIO.MakeDirectory(m_sDbDir); 44 | 45 | return FileIO.FileExists(m_sDbDir); 46 | } 47 | 48 | //------------------------------------------------------------------------------------------------ 49 | override EDF_EDbOperationStatusCode AddOrUpdate(notnull EDF_DbEntity entity) 50 | { 51 | if (!entity.HasId()) 52 | return EDF_EDbOperationStatusCode.FAILURE_ID_NOT_SET; 53 | 54 | FileIO.MakeDirectory(_GetTypeDirectory(entity.Type())); 55 | 56 | EDF_EDbOperationStatusCode statusCode = WriteToDisk(entity); 57 | if (statusCode != EDF_EDbOperationStatusCode.SUCCESS) 58 | return statusCode; 59 | 60 | if (m_bUseCache) 61 | m_pEntityCache.Add(entity); 62 | 63 | // Add id to pool of all known ids 64 | GetIdsByType(entity.Type()).Insert(entity.GetId()); 65 | 66 | return EDF_EDbOperationStatusCode.SUCCESS; 67 | } 68 | 69 | //------------------------------------------------------------------------------------------------ 70 | override EDF_EDbOperationStatusCode Remove(typename entityType, string entityId) 71 | { 72 | if (!entityId) return EDF_EDbOperationStatusCode.FAILURE_ID_NOT_SET; 73 | 74 | EDF_EDbOperationStatusCode statusCode = DeleteFromDisk(entityType, entityId); 75 | if (statusCode != EDF_EDbOperationStatusCode.SUCCESS) 76 | return statusCode; 77 | 78 | if (m_bUseCache) 79 | m_pEntityCache.Remove(entityId); 80 | 81 | // Remove id from pool of all known ids 82 | set ids = GetIdsByType(entityType); 83 | ids.RemoveItem(entityId); 84 | 85 | // If collection of that entity type is empty remove the folder too to keep the file structure clean 86 | if (ids.IsEmpty()) 87 | FileIO.DeleteFile(_GetTypeDirectory(entityType)); 88 | 89 | return EDF_EDbOperationStatusCode.SUCCESS; 90 | } 91 | 92 | //------------------------------------------------------------------------------------------------ 93 | override EDF_DbFindResultMultiple FindAll(typename entityType, EDF_DbFindCondition condition = null, array orderBy = null, int limit = -1, int offset = -1) 94 | { 95 | // See if we can only load selected few entities by id or we need the entire collection to search through 96 | set loadIds(), skipIds(); 97 | bool needsFilter = false; 98 | if (!EDF_DbFindConditionEvaluator.CollectConditionIds(condition, loadIds, skipIds) || loadIds.IsEmpty() || !skipIds.IsEmpty()) 99 | { 100 | // Condition(s) require more information than just ids so all need to be loaded and also filtered by condition 101 | loadIds = GetIdsByType(entityType, skipIds); 102 | needsFilter = true; 103 | } 104 | 105 | array entities(); 106 | 107 | foreach (string entityId : loadIds) 108 | { 109 | EDF_DbEntity entity; 110 | 111 | if (m_bUseCache) 112 | entity = m_pEntityCache.Get(entityId); 113 | 114 | if (!entity) 115 | { 116 | EDF_EDbOperationStatusCode statusCode = ReadFromDisk(entityType, entityId, entity); 117 | 118 | if (statusCode != EDF_EDbOperationStatusCode.SUCCESS || !entity) 119 | continue; 120 | 121 | if (m_bUseCache) 122 | m_pEntityCache.Add(entity); 123 | } 124 | 125 | entities.Insert(entity); 126 | } 127 | 128 | if (needsFilter && condition) 129 | entities = EDF_DbFindConditionEvaluator.GetFiltered(entities, condition); 130 | 131 | if (orderBy) 132 | entities = EDF_DbEntitySorter.GetSorted(entities, orderBy); 133 | 134 | array resultEntites(); 135 | 136 | foreach (int idx, EDF_DbEntity entity : entities) 137 | { 138 | // Respect output limit is specified 139 | if (limit != -1 && resultEntites.Count() >= limit) 140 | break; 141 | 142 | // Skip the first n records if offset specified (for paginated loading together with limit) 143 | if (offset != -1 && idx < offset) 144 | continue; 145 | 146 | resultEntites.Insert(entity); 147 | } 148 | 149 | return new EDF_DbFindResultMultiple(EDF_EDbOperationStatusCode.SUCCESS, resultEntites); 150 | } 151 | 152 | //------------------------------------------------------------------------------------------------ 153 | override void AddOrUpdateAsync(notnull EDF_DbEntity entity, EDF_DbOperationStatusOnlyCallback callback = null) 154 | { 155 | // FileIO is blocking, re-use sync api 156 | EDF_EDbOperationStatusCode statusCode = AddOrUpdate(entity); 157 | if (callback) 158 | callback.Invoke(statusCode); 159 | } 160 | 161 | //------------------------------------------------------------------------------------------------ 162 | override void RemoveAsync(typename entityType, string entityId, EDF_DbOperationStatusOnlyCallback callback = null) 163 | { 164 | // FileIO is blocking, re-use sync api 165 | EDF_EDbOperationStatusCode statusCode = Remove(entityType, entityId); 166 | if (callback) 167 | callback.Invoke(statusCode); 168 | } 169 | 170 | //------------------------------------------------------------------------------------------------ 171 | override void FindAllAsync(typename entityType, EDF_DbFindCondition condition = null, array orderBy = null, int limit = -1, int offset = -1, EDF_DbFindCallbackBase callback = null) 172 | { 173 | // FileIO is blocking, re-use sync api 174 | EDF_DbFindResultMultiple findResults = FindAll(entityType, condition, orderBy, limit, offset); 175 | if (callback) 176 | callback.Invoke(findResults.GetStatusCode(), findResults.GetEntities()); 177 | } 178 | 179 | //------------------------------------------------------------------------------------------------ 180 | protected set GetIdsByType(typename entityType, set skipIds = null) 181 | { 182 | set ids = m_mEntityIdsyCache.Get(entityType); 183 | if (!ids) 184 | { 185 | ids = GetIdsOnDisk(entityType, skipIds); 186 | m_mEntityIdsyCache.Set(entityType, ids); 187 | } 188 | return ids; 189 | } 190 | 191 | //------------------------------------------------------------------------------------------------ 192 | protected set GetIdsOnDisk(typename entityType, set skipIds = null) 193 | { 194 | EDF_FileDbDriverFindIdsCallback callback(); 195 | FileIO.FindFiles(callback.AddFile, _GetTypeDirectory(entityType), GetFileExtension()); 196 | 197 | set ids(); 198 | foreach (string id : callback.m_Ids) 199 | { 200 | if (!skipIds || !skipIds.Contains(id)) 201 | ids.Insert(id); 202 | } 203 | return ids; 204 | } 205 | 206 | //------------------------------------------------------------------------------------------------ 207 | string _GetTypeDirectory(typename entityType) 208 | { 209 | string entityName = EDF_DbName.Get(entityType); 210 | 211 | if (entityName.EndsWith("y")) 212 | { 213 | entityName = string.Format("%1ies", entityName.Substring(0, entityName.Length() - 1)); 214 | } 215 | else if(!entityName.EndsWith("s")) 216 | { 217 | entityName += "s"; 218 | } 219 | 220 | return string.Format("%1/%2", m_sDbDir, entityName); 221 | } 222 | 223 | //------------------------------------------------------------------------------------------------ 224 | protected string GetFileExtension(); 225 | 226 | //------------------------------------------------------------------------------------------------ 227 | protected EDF_EDbOperationStatusCode WriteToDisk(EDF_DbEntity entity); 228 | 229 | //------------------------------------------------------------------------------------------------ 230 | protected EDF_EDbOperationStatusCode ReadFromDisk(typename entityType, string entityId, out EDF_DbEntity entity); 231 | 232 | //------------------------------------------------------------------------------------------------ 233 | protected EDF_EDbOperationStatusCode DeleteFromDisk(typename entityType, string entityId) 234 | { 235 | string file = string.Format("%1/%2%3", _GetTypeDirectory(entityType), entityId, GetFileExtension()); 236 | 237 | if (!FileIO.FileExists(file)) 238 | return EDF_EDbOperationStatusCode.FAILURE_ID_NOT_FOUND; 239 | 240 | if (!FileIO.DeleteFile(file)) 241 | return EDF_EDbOperationStatusCode.FAILURE_DB_UNAVAILABLE; 242 | 243 | return EDF_EDbOperationStatusCode.SUCCESS; 244 | } 245 | 246 | //------------------------------------------------------------------------------------------------ 247 | EDF_DbEntityCache _GetEntityCache() 248 | { 249 | return m_pEntityCache; 250 | } 251 | 252 | //------------------------------------------------------------------------------------------------ 253 | map> _GetEntityIds() 254 | { 255 | return m_mEntityIdsyCache; 256 | } 257 | }; 258 | 259 | class EDF_FileDbDriverFindIdsCallback 260 | { 261 | ref array m_Ids = {}; 262 | 263 | //------------------------------------------------------------------------------------------------ 264 | void AddFile(string fileName, FileAttribute attributes = 0, string filesystem = string.Empty) 265 | { 266 | fileName = FilePath.StripPath(fileName); 267 | fileName = FilePath.StripExtension(fileName); 268 | 269 | // Not a UUID of 36 chars length 270 | if (fileName.IsEmpty() || fileName.Length() != 36) 271 | return; 272 | 273 | m_Ids.Insert(fileName); 274 | } 275 | }; 276 | -------------------------------------------------------------------------------- /src/Scripts/Game/Drivers/LocalFile/EDF_JsonFileDbDriver.c: -------------------------------------------------------------------------------- 1 | [EDF_DbConnectionInfoDriverType(EDF_JsonFileDbDriver), BaseContainerProps()] 2 | class EDF_JsonFileDbConnectionInfo : EDF_FileDbDriverInfoBase 3 | { 4 | [Attribute(defvalue: "1", desc: "Save files prettified (formatted). Primarily for debugging.")] 5 | bool m_bPrettify; 6 | 7 | //------------------------------------------------------------------------------------------------ 8 | override void ReadOptions(string connectionString) 9 | { 10 | super.ReadOptions(connectionString); 11 | m_bPrettify = connectionString.Contains("prettify=true") || connectionString.Contains("pretty=true"); 12 | } 13 | }; 14 | 15 | [EDF_DbDriverName({"JsonFile", "Json"})] 16 | class EDF_JsonFileDbDriver : EDF_FileDbDriverBase 17 | { 18 | protected bool m_bPrettify; 19 | 20 | //------------------------------------------------------------------------------------------------ 21 | override bool Initialize(notnull EDF_DbConnectionInfoBase connectionInfo) 22 | { 23 | if (!super.Initialize(connectionInfo)) 24 | return false; 25 | 26 | auto jsonConnectInfo = EDF_JsonFileDbConnectionInfo.Cast(connectionInfo); 27 | m_bPrettify = jsonConnectInfo.m_bPrettify; 28 | return true; 29 | } 30 | 31 | //------------------------------------------------------------------------------------------------ 32 | override protected string GetFileExtension() 33 | { 34 | return ".json"; 35 | } 36 | 37 | //------------------------------------------------------------------------------------------------ 38 | override protected EDF_EDbOperationStatusCode WriteToDisk(EDF_DbEntity entity) 39 | { 40 | ContainerSerializationSaveContext writer(); 41 | BaseJsonSerializationSaveContainer jsonContainer; 42 | if (m_bPrettify) 43 | { 44 | jsonContainer = new PrettyJsonSaveContainer(); 45 | } 46 | else 47 | { 48 | jsonContainer = new JsonSaveContainer(); 49 | } 50 | 51 | jsonContainer.SetMaxDecimalPlaces(5); 52 | writer.SetContainer(jsonContainer); 53 | 54 | if (!writer.WriteValue("", entity)) 55 | return EDF_EDbOperationStatusCode.FAILURE_DATA_MALFORMED; 56 | 57 | if (!jsonContainer.SaveToFile(string.Format("%1/%2.json", _GetTypeDirectory(entity.Type()), entity.GetId()))) 58 | return EDF_EDbOperationStatusCode.FAILURE_DB_UNAVAILABLE; 59 | 60 | return EDF_EDbOperationStatusCode.SUCCESS; 61 | } 62 | 63 | //------------------------------------------------------------------------------------------------ 64 | override protected EDF_EDbOperationStatusCode ReadFromDisk(typename entityType, string entityId, out EDF_DbEntity entity) 65 | { 66 | string file = string.Format("%1/%2.json", _GetTypeDirectory(entityType), entityId); 67 | if (FileIO.FileExists(file)) 68 | { 69 | SCR_JsonLoadContext reader(); 70 | if (!reader.LoadFromFile(file)) 71 | return EDF_EDbOperationStatusCode.FAILURE_DB_UNAVAILABLE; 72 | 73 | entity = EDF_DbEntity.Cast(entityType.Spawn()); 74 | if (!reader.ReadValue("", entity)) 75 | return EDF_EDbOperationStatusCode.FAILURE_DATA_MALFORMED; 76 | } 77 | 78 | return EDF_EDbOperationStatusCode.SUCCESS; 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /src/Scripts/Game/Drivers/WebProxy/EDF_MongoDbDriver.c: -------------------------------------------------------------------------------- 1 | [EDF_DbConnectionInfoDriverType(EDF_MongoDbDriver), BaseContainerProps()] 2 | class EDF_MongoDbConnectionInfo : EDF_WebProxyConnectionInfoBase 3 | { 4 | } 5 | 6 | [EDF_DbDriverName({"MongoDb", "Mongo"})] 7 | class EDF_MongoDbDriver : EDF_WebProxyDbDriver 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /src/Scripts/Game/Drivers/WebProxy/EDF_WebProxyDbDriverBase.c: -------------------------------------------------------------------------------- 1 | [BaseContainerProps()] 2 | class EDF_WebProxyConnectionInfoBase : EDF_DbConnectionInfoBase 3 | { 4 | [Attribute(defvalue: "localhost", desc: "Web proxy hostname.")] 5 | string m_sProxyHost; 6 | 7 | [Attribute(defvalue: "8008", desc: "Web proxy port.")] 8 | int m_iProxyPort; 9 | 10 | [Attribute(desc: "Use TLS/SSL to connect to the web proxy.")] 11 | bool m_bSecureConnection; 12 | 13 | [Attribute(desc: "Custom headers for all requests e.g. api keys.")] 14 | ref array m_aHeaders; 15 | 16 | [Attribute(desc: "Additional parameters added to the url with ...&key=value")] 17 | ref array m_aParameters; 18 | 19 | //------------------------------------------------------------------------------------------------ 20 | override void ReadOptions(string connectionString) 21 | { 22 | super.ReadOptions(connectionString); 23 | 24 | if (m_sDatabaseName.Length() == connectionString.Length()) 25 | return; // No other params 26 | 27 | if (!m_aHeaders) 28 | m_aHeaders = {}; 29 | 30 | if (!m_aParameters) 31 | m_aParameters = {}; 32 | 33 | array keyValuePairs(); 34 | int paramsStart = m_sDatabaseName.Length() + 1; 35 | connectionString.Substring(paramsStart, connectionString.Length() - paramsStart).Split("&", keyValuePairs, true); 36 | foreach (string keyValuePair : keyValuePairs) 37 | { 38 | int keyIdx = keyValuePair.IndexOf("="); 39 | if (keyIdx == -1) 40 | continue; 41 | 42 | string key = keyValuePair.Substring(0, keyIdx).Trim(); 43 | string keyLower = key; 44 | keyLower.ToLower(); 45 | 46 | int valueFrom = keyIdx + 1; 47 | string value = keyValuePair.Substring(valueFrom, keyValuePair.Length() - valueFrom).Trim(); 48 | string valueLower = value; 49 | valueLower.ToLower(); 50 | 51 | switch (keyLower) 52 | { 53 | case "host": 54 | case "proxyhost": 55 | { 56 | m_sProxyHost = value; 57 | break; 58 | } 59 | 60 | case "port": 61 | case "proxyport": 62 | { 63 | m_iProxyPort = value.ToInt(8001); 64 | break; 65 | } 66 | 67 | case "ssl": 68 | case "tls": 69 | case "secure": 70 | case "secureconnection": 71 | { 72 | m_bSecureConnection = valueLower == "1" || valueLower == "true" || valueLower == "yes"; 73 | break; 74 | } 75 | 76 | case "headers": 77 | case "parameters": 78 | { 79 | array kvs(); 80 | value.Split(",", kvs, true); 81 | if ((kvs.Count() % 2) != 0) 82 | { 83 | Debug.Error(string.Format("Invalid '%1' connection info parameter. Not all keys have a value!", key)); 84 | break; 85 | } 86 | 87 | bool isHeaders = keyLower == "headers"; 88 | 89 | for (int nKey = 0, count = kvs.Count() - 1; nKey < count; nKey+=2) 90 | { 91 | auto param = new EDF_WebProxyParameter(kvs.Get(nKey), kvs.Get(nKey + 1)); 92 | 93 | if (isHeaders) 94 | { 95 | m_aHeaders.Insert(param); 96 | continue; 97 | } 98 | 99 | m_aParameters.Insert(param); 100 | } 101 | break; 102 | } 103 | 104 | default: 105 | { 106 | // Backwards compatiblity, will remove it at some point 107 | m_aParameters.Insert(new EDF_WebProxyParameter(key, value)); 108 | 109 | //Debug.Error(string.Format("Unknown parameter '%1'='%2' in connection info.", key, value)); 110 | } 111 | } 112 | } 113 | } 114 | } 115 | 116 | sealed class EDF_CustomDefaultTitle : BaseContainerCustomTitleField 117 | { 118 | protected string m_sDefaultTitle; 119 | 120 | //------------------------------------------------------------------------------------------------ 121 | override bool _WB_GetCustomTitle(BaseContainer source, out string title) 122 | { 123 | super._WB_GetCustomTitle(source, title); 124 | if (!title) 125 | title = m_sDefaultTitle; 126 | 127 | return true; 128 | } 129 | 130 | //------------------------------------------------------------------------------------------------ 131 | void EDF_CustomDefaultTitle(string propertyName, string defaultTitle) 132 | { 133 | m_PropertyName = propertyName; 134 | m_sDefaultTitle = defaultTitle; 135 | } 136 | } 137 | 138 | [EDF_CustomDefaultTitle("m_sKey", "UNCONFIGURED"), BaseContainerProps()] 139 | sealed class EDF_WebProxyParameter 140 | { 141 | [Attribute()] 142 | string m_sKey; 143 | 144 | [Attribute()] 145 | string m_sValue; 146 | 147 | //------------------------------------------------------------------------------------------------ 148 | void EDF_WebProxyParameter(string key, string value) 149 | { 150 | // Only use ctor params if they provide something. Otherwise it was set via attribute already. 151 | if (key) 152 | m_sKey = key; 153 | 154 | if (value) 155 | m_sValue = value; 156 | } 157 | } 158 | 159 | sealed class EDF_WebProxyDbDriverCallback : RestCallback 160 | { 161 | protected static ref set s_aSelfReferences = new set(); 162 | 163 | protected ref EDF_DbOperationCallback m_pCallback; 164 | protected typename m_tResultType; 165 | 166 | protected string m_sVerb; 167 | protected string m_sUrl; 168 | 169 | //------------------------------------------------------------------------------------------------ 170 | override void OnSuccess(string data, int dataSize) 171 | { 172 | #ifdef PERSISTENCE_DEBUG 173 | Print(string.Format("%1::OnSuccess(%2, %3) from %4:%5", this, dataSize, data, m_sVerb, m_sUrl), LogLevel.VERBOSE); 174 | #endif 175 | 176 | s_aSelfReferences.RemoveItem(this); 177 | 178 | auto statusCallback = EDF_DbOperationStatusOnlyCallback.Cast(m_pCallback); 179 | if (statusCallback) 180 | { 181 | statusCallback.Invoke(EDF_EDbOperationStatusCode.SUCCESS); 182 | return; 183 | } 184 | 185 | auto findCallback = EDF_DbFindCallbackBase.Cast(m_pCallback); 186 | if (!findCallback) 187 | return; // Could have been a status only operation but no callback was set 188 | 189 | if (dataSize == 0) 190 | { 191 | OnFailure(EDF_EDbOperationStatusCode.FAILURE_RESPONSE_MALFORMED); 192 | return; 193 | } 194 | 195 | SCR_JsonLoadContext reader(); 196 | array resultEntities(); 197 | 198 | // Read per line individually until json load context has polymorph support: https://feedback.bistudio.com/T173074 199 | array lines(); 200 | data.Split("\n", lines, true); 201 | for (int nLine = 1, count = lines.Count() - 1; nLine < count; nLine++) 202 | { 203 | EDF_DbEntity entity = EDF_DbEntity.Cast(m_tResultType.Spawn()); 204 | 205 | if (!reader.ImportFromString(lines[nLine]) || !reader.ReadValue("", entity)) 206 | { 207 | OnFailure(EDF_EDbOperationStatusCode.FAILURE_RESPONSE_MALFORMED); 208 | return; 209 | } 210 | 211 | resultEntities.Insert(entity); 212 | } 213 | 214 | findCallback.Invoke(EDF_EDbOperationStatusCode.SUCCESS, resultEntities); 215 | }; 216 | 217 | //------------------------------------------------------------------------------------------------ 218 | override void OnError(int errorCode) 219 | { 220 | s_aSelfReferences.RemoveItem(this); 221 | 222 | #ifdef PERSISTENCE_DEBUG 223 | Print(string.Format("%1::OnError(%2) from %3:%4", this, typename.EnumToString(ERestResult, errorCode), m_sVerb, m_sUrl), LogLevel.ERROR); 224 | #endif 225 | 226 | EDF_EDbOperationStatusCode statusCode; 227 | switch (errorCode) 228 | { 229 | default: 230 | { 231 | statusCode = EDF_EDbOperationStatusCode.FAILURE_UNKNOWN; 232 | break; 233 | } 234 | } 235 | 236 | OnFailure(statusCode); 237 | } 238 | 239 | //------------------------------------------------------------------------------------------------ 240 | override void OnTimeout() 241 | { 242 | s_aSelfReferences.RemoveItem(this); 243 | 244 | #ifdef PERSISTENCE_DEBUG 245 | Print(string.Format("%1::OnTimeout() from %2:%3", this, m_sVerb, m_sUrl), LogLevel.VERBOSE); 246 | #endif 247 | 248 | OnFailure(EDF_EDbOperationStatusCode.FAILURE_DB_UNAVAILABLE); 249 | } 250 | 251 | //------------------------------------------------------------------------------------------------ 252 | protected void OnFailure(EDF_EDbOperationStatusCode statusCode) 253 | { 254 | auto statusCallback = EDF_DbOperationStatusOnlyCallback.Cast(m_pCallback); 255 | if (statusCallback) 256 | { 257 | statusCallback.Invoke(statusCode); 258 | return; 259 | } 260 | 261 | auto findCallback = EDF_DbFindCallbackBase.Cast(m_pCallback); 262 | if (findCallback) 263 | findCallback.Invoke(EDF_EDbOperationStatusCode.FAILURE_UNKNOWN, new array); 264 | }; 265 | 266 | //------------------------------------------------------------------------------------------------ 267 | static void Reset() 268 | { 269 | s_aSelfReferences = new set(); 270 | } 271 | 272 | //------------------------------------------------------------------------------------------------ 273 | void EDF_WebProxyDbDriverCallback(EDF_DbOperationCallback callback, typename resultType = typename.Empty, string verb = string.Empty, string url = string.Empty) 274 | { 275 | m_pCallback = callback; 276 | m_tResultType = resultType; 277 | m_sVerb = verb; 278 | m_sUrl = url; 279 | s_aSelfReferences.Insert(this); 280 | }; 281 | } 282 | 283 | sealed class EDF_WebProxyDbDriverFindRequest 284 | { 285 | EDF_DbFindCondition m_pCondition; 286 | ref array m_aOrderBy; 287 | int m_iLimit; 288 | int m_iOffset; 289 | 290 | //------------------------------------------------------------------------------------------------ 291 | protected bool SerializationSave(BaseSerializationSaveContext saveContext) 292 | { 293 | if (m_pCondition) 294 | saveContext.WriteValue("condition", m_pCondition); 295 | 296 | if (m_aOrderBy) 297 | saveContext.WriteValue("orderBy", m_aOrderBy); 298 | 299 | if (m_iLimit != -1) 300 | saveContext.WriteValue("limit", m_iLimit); 301 | 302 | if (m_iOffset != -1) 303 | saveContext.WriteValue("offset", m_iOffset); 304 | 305 | return true; 306 | } 307 | 308 | //------------------------------------------------------------------------------------------------ 309 | void EDF_WebProxyDbDriverFindRequest(EDF_DbFindCondition condition, array orderBy, int limit, int offset) 310 | { 311 | m_pCondition = condition; 312 | m_aOrderBy = orderBy; 313 | m_iLimit = limit; 314 | m_iOffset = offset; 315 | } 316 | } 317 | 318 | class EDF_WebProxyDbDriver : EDF_DbDriver 319 | { 320 | protected RestContext m_pContext; 321 | protected string m_sAddtionalParams; 322 | 323 | //------------------------------------------------------------------------------------------------ 324 | override bool Initialize(notnull EDF_DbConnectionInfoBase connectionInfo) 325 | { 326 | EDF_WebProxyConnectionInfoBase webConnectInfo = EDF_WebProxyConnectionInfoBase.Cast(connectionInfo); 327 | string url = "http"; 328 | 329 | if (webConnectInfo.m_bSecureConnection) 330 | url += "s"; 331 | 332 | // !!! Must have trailing / or the blocking methods don't like invoking it 333 | url += string.Format("://%1:%2/%3/", webConnectInfo.m_sProxyHost, webConnectInfo.m_iProxyPort, webConnectInfo.m_sDatabaseName); 334 | m_pContext = GetGame().GetRestApi().GetContext(url); 335 | 336 | string headers; 337 | bool hasContentType, hasUserAgent; 338 | if (webConnectInfo.m_aHeaders) 339 | { 340 | foreach (EDF_WebProxyParameter header : webConnectInfo.m_aHeaders) 341 | { 342 | if (!hasContentType && (header.m_sKey.Compare("Content-Type", false) == 0)) 343 | hasContentType = true; 344 | 345 | if (!hasUserAgent && (header.m_sKey.Compare("User-Agent", false) == 0)) 346 | hasUserAgent = true; 347 | 348 | if (!headers.IsEmpty()) 349 | headers += ","; 350 | 351 | headers += string.Format("%1,%2", header.m_sKey, header.m_sValue); 352 | } 353 | } 354 | 355 | if (!hasContentType) 356 | { 357 | if (!headers.IsEmpty()) 358 | headers += ","; 359 | 360 | headers += "Content-Type,application/json"; 361 | } 362 | 363 | if (!hasUserAgent) 364 | headers += ",User-Agent,AR-EDF"; 365 | 366 | m_pContext.SetHeaders(headers); 367 | 368 | if (webConnectInfo.m_aParameters) 369 | { 370 | int paramCount = webConnectInfo.m_aParameters.Count(); 371 | if (paramCount > 0) 372 | { 373 | m_sAddtionalParams += "?"; 374 | 375 | foreach (int idx, EDF_WebProxyParameter parameter : webConnectInfo.m_aParameters) 376 | { 377 | m_sAddtionalParams += string.Format("%1=%2", parameter.m_sKey, parameter.m_sValue); 378 | if (idx < paramCount - 1) 379 | m_sAddtionalParams += "&"; 380 | } 381 | } 382 | } 383 | 384 | return true; 385 | } 386 | 387 | //------------------------------------------------------------------------------------------------ 388 | override EDF_EDbOperationStatusCode AddOrUpdate(notnull EDF_DbEntity entity) 389 | { 390 | typename entityType = entity.Type(); 391 | string request = string.Format("%1/%2%3", EDF_DbName.Get(entityType), entity.GetId(), m_sAddtionalParams); 392 | string data = Serialize(entity); 393 | m_pContext.PUT_now(request, data); 394 | return EDF_EDbOperationStatusCode.SUCCESS; 395 | } 396 | 397 | //------------------------------------------------------------------------------------------------ 398 | override EDF_EDbOperationStatusCode Remove(typename entityType, string entityId) 399 | { 400 | string request = string.Format("%1/%2%3", EDF_DbName.Get(entityType), entityId, m_sAddtionalParams); 401 | m_pContext.DELETE_now(request, string.Empty); 402 | return EDF_EDbOperationStatusCode.SUCCESS; 403 | } 404 | 405 | //------------------------------------------------------------------------------------------------ 406 | override EDF_DbFindResultMultiple FindAll(typename entityType, EDF_DbFindCondition condition = null, array orderBy = null, int limit = -1, int offset = -1) 407 | { 408 | // Can not be implemented until https://feedback.bistudio.com/T166390 is fixed. Hopefully AR 0.9.9 or 0.9.10 409 | return new EDF_DbFindResultMultiple(EDF_EDbOperationStatusCode.FAILURE_NOT_IMPLEMENTED, {}); 410 | } 411 | 412 | //------------------------------------------------------------------------------------------------ 413 | override void AddOrUpdateAsync(notnull EDF_DbEntity entity, EDF_DbOperationStatusOnlyCallback callback = null) 414 | { 415 | if (s_bForceBlocking) 416 | { 417 | EDF_EDbOperationStatusCode statusCode = AddOrUpdate(entity); 418 | if (callback) 419 | callback.Invoke(statusCode); 420 | 421 | return; 422 | } 423 | 424 | typename entityType = entity.Type(); 425 | string request = string.Format("%1/%2%3", EDF_DbName.Get(entityType), entity.GetId(), m_sAddtionalParams); 426 | string data = Serialize(entity); 427 | m_pContext.PUT(new EDF_WebProxyDbDriverCallback(callback, verb: "PUT", url: request), request, data); 428 | } 429 | 430 | //------------------------------------------------------------------------------------------------ 431 | override void RemoveAsync(typename entityType, string entityId, EDF_DbOperationStatusOnlyCallback callback = null) 432 | { 433 | if (s_bForceBlocking) 434 | { 435 | EDF_EDbOperationStatusCode statusCode = Remove(entityType, entityId); 436 | if (callback) 437 | callback.Invoke(statusCode); 438 | 439 | return; 440 | } 441 | 442 | string request = string.Format("%1/%2%3", EDF_DbName.Get(entityType), entityId, m_sAddtionalParams); 443 | m_pContext.DELETE(new EDF_WebProxyDbDriverCallback(callback, verb: "DELETE", url: request), request, string.Empty); 444 | } 445 | 446 | //------------------------------------------------------------------------------------------------ 447 | override void FindAllAsync(typename entityType, EDF_DbFindCondition condition = null, array orderBy = null, int limit = -1, int offset = -1, EDF_DbFindCallbackBase callback = null) 448 | { 449 | /* 450 | EDF_DbFindFieldCondition findField = EDF_DbFindFieldCondition.Cast(condition); 451 | if (findField && findField.m_bUsesTypename) 452 | { 453 | Debug.Error("Typename usage in find conditions currently unsupported by this driver."); 454 | if (callback) 455 | callback.Invoke(EDF_EDbOperationStatusCode.FAILURE_NOT_IMPLEMENTED, new array()); 456 | } 457 | */ 458 | 459 | if (s_bForceBlocking) 460 | { 461 | EDF_DbFindResultMultiple results = FindAll(entityType, condition, orderBy, limit, offset); 462 | if (callback) 463 | callback.Invoke(results.GetStatusCode(), results.GetEntities()); 464 | 465 | return; 466 | } 467 | 468 | string request = string.Format("%1%2", EDF_DbName.Get(entityType), m_sAddtionalParams); 469 | string data = Serialize(new EDF_WebProxyDbDriverFindRequest(condition, orderBy, limit, offset)); 470 | //Print(request); 471 | //Print(data); 472 | //System.ExportToClipboard(data); 473 | m_pContext.POST(new EDF_WebProxyDbDriverCallback(callback, entityType, verb: "POST", url: request), request, data); 474 | } 475 | 476 | //------------------------------------------------------------------------------------------------ 477 | static string Serialize(Managed data) 478 | { 479 | ContainerSerializationSaveContext writer(); 480 | JsonSaveContainer jsonContainer = new JsonSaveContainer(); 481 | jsonContainer.SetMaxDecimalPlaces(5); 482 | writer.SetContainer(jsonContainer); 483 | writer.WriteValue("", data); 484 | return jsonContainer.ExportToString(); 485 | } 486 | 487 | //------------------------------------------------------------------------------------------------ 488 | //! TODO: Rely on https://feedback.bistudio.com/T173074 instead once added 489 | static string MoveTypeDiscriminatorIn(string data, string discriminator = "_type") 490 | { 491 | string quoutedDiscriminator = string.Format("\"%1\"", discriminator); 492 | int typeDiscriminatorIdx = data.IndexOf(quoutedDiscriminator); 493 | if (typeDiscriminatorIdx == -1) 494 | return data; 495 | 496 | string processedString; 497 | int dataPosition; 498 | int dataLength = data.Length(); 499 | 500 | while (true) 501 | { 502 | // Add content until discriminator 503 | processedString += data.Substring(dataPosition, typeDiscriminatorIdx - dataPosition); 504 | 505 | // Extract discriminator 506 | int discriminatorLength = data.IndexOfFrom(typeDiscriminatorIdx, ",") - typeDiscriminatorIdx + 1; 507 | string typeDiscrimiantor = data.Substring(typeDiscriminatorIdx, discriminatorLength); 508 | 509 | // Read remaining data until injection point 510 | int injectionIndex = data.IndexOfFrom(typeDiscriminatorIdx + discriminatorLength, "{") + 1; 511 | int skipOver = typeDiscriminatorIdx + discriminatorLength; 512 | processedString += data.Substring(skipOver, injectionIndex - skipOver); 513 | 514 | // Inject discriminator 515 | processedString += typeDiscrimiantor; 516 | 517 | dataPosition = injectionIndex; 518 | typeDiscriminatorIdx = data.IndexOfFrom(skipOver, quoutedDiscriminator); 519 | if (typeDiscriminatorIdx == -1) 520 | { 521 | processedString += data.Substring(dataPosition, dataLength - dataPosition); 522 | break; 523 | } 524 | } 525 | 526 | return processedString; 527 | } 528 | 529 | //------------------------------------------------------------------------------------------------ 530 | static string MoveTypeDiscriminatorOut(string data, string discriminator = "_type") 531 | { 532 | string quoutedDiscriminator = string.Format("\"%1\"", discriminator); 533 | int typeDiscriminatorIdx = data.IndexOf(quoutedDiscriminator); 534 | if (typeDiscriminatorIdx == -1) 535 | return data; 536 | 537 | string processedString; 538 | int dataPosition; 539 | int dataLength = data.Length(); 540 | 541 | while (true) 542 | { 543 | // Extract discriminator 544 | int discriminatorLength = data.IndexOfFrom(typeDiscriminatorIdx, ",") - typeDiscriminatorIdx + 1; 545 | string typeDiscrimiantor = data.Substring(typeDiscriminatorIdx, discriminatorLength); 546 | 547 | // Find injection position 548 | int injectAt = -1, remainingBrackes = 2; 549 | for (int nChar = typeDiscriminatorIdx - 1; nChar >= 0; nChar--) 550 | { 551 | if ((data.Get(nChar) == "\"") && (--remainingBrackes == 0)) 552 | { 553 | injectAt = nChar; 554 | break; 555 | } 556 | } 557 | 558 | // Get data in front 559 | processedString += data.Substring(dataPosition, injectAt - dataPosition); 560 | 561 | // Inject type discriminator 562 | processedString += typeDiscrimiantor; 563 | 564 | // Add data from in between 565 | processedString += data.Substring(injectAt, typeDiscriminatorIdx - injectAt); 566 | 567 | // Continue behind original position 568 | dataPosition = typeDiscriminatorIdx + discriminatorLength; 569 | 570 | typeDiscriminatorIdx = data.IndexOfFrom(dataPosition, quoutedDiscriminator); 571 | if (typeDiscriminatorIdx == -1) 572 | { 573 | processedString += data.Substring(dataPosition, dataLength - dataPosition); 574 | break; 575 | } 576 | } 577 | 578 | return processedString; 579 | } 580 | 581 | //------------------------------------------------------------------------------------------------ 582 | void ~EDF_WebProxyDbDriver() 583 | { 584 | if (!m_pContext) 585 | return; 586 | 587 | m_pContext.reset(); 588 | m_pContext = null; 589 | } 590 | } 591 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_BufferedDbContext.c: -------------------------------------------------------------------------------- 1 | /* 2 | class EDF_BufferedDbContext : EDF_DbContext 3 | { 4 | //------------------------------------------------------------------------------------------------ 5 | void Flush(int maxBatchSize = 50) 6 | { 7 | EDF_DbDriverBufferWrapper.Cast(m_Driver).Flush(maxBatchSize); 8 | } 9 | 10 | //------------------------------------------------------------------------------------------------ 11 | override static EDF_BufferedDbContext Create(notnull EDF_DbConnectionInfoBase connectionInfo) 12 | { 13 | EDF_DbContext baseContext = EDF_DbContext.Create(connectionInfo); 14 | if (baseContext) 15 | return new EDF_BufferedDbContext(baseContext.m_Driver); 16 | 17 | return null; 18 | } 19 | 20 | //------------------------------------------------------------------------------------------------ 21 | protected void EDF_BufferedDbContext(notnull EDF_DbDriver driver) 22 | { 23 | m_Driver = new EDF_DbDriverBufferWrapper(driver); 24 | } 25 | }; 26 | */ -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_Callback.c: -------------------------------------------------------------------------------- 1 | //! Base class for scripted callbacks. Primarily used for async operations 2 | class EDF_Callback 3 | { 4 | ref Managed m_pContext; 5 | Class m_pInvokeInstance; 6 | string m_sInvokeMethod; 7 | 8 | //------------------------------------------------------------------------------------------------ 9 | sealed void Invoke() 10 | { 11 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, m_pContext); 12 | } 13 | 14 | //------------------------------------------------------------------------------------------------ 15 | void EDF_Callback(Class invokeInstance = null, string invokeMethod = string.Empty, Managed context = null) 16 | { 17 | m_pInvokeInstance = invokeInstance; 18 | m_sInvokeMethod = invokeMethod; 19 | m_pContext = context; 20 | } 21 | }; 22 | 23 | class EDF_DataCallbackSingle : EDF_Callback 24 | { 25 | //------------------------------------------------------------------------------------------------ 26 | void OnComplete(T data, Managed context); 27 | 28 | //------------------------------------------------------------------------------------------------ 29 | sealed void Invoke(T data) 30 | { 31 | if (!m_pInvokeInstance || 32 | !m_sInvokeMethod || 33 | !GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, data, m_pContext)) 34 | { 35 | OnComplete(data, m_pContext); 36 | } 37 | } 38 | }; 39 | 40 | class EDF_DataCallbackMultiple : EDF_Callback 41 | { 42 | //------------------------------------------------------------------------------------------------ 43 | void OnComplete(array data, Managed context); 44 | 45 | //------------------------------------------------------------------------------------------------ 46 | sealed void Invoke(array data) 47 | { 48 | if (!m_pInvokeInstance || 49 | !m_sInvokeMethod || 50 | !GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, data, m_pContext)) 51 | { 52 | OnComplete(data, m_pContext); 53 | } 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_DbContext.c: -------------------------------------------------------------------------------- 1 | class EDF_DbContext 2 | { 3 | protected ref EDF_DbDriver m_Driver; 4 | 5 | //------------------------------------------------------------------------------------------------ 6 | //! Adds a new entry to the database or updates an existing one 7 | //! \param entity database entity to add or update 8 | //! \return status code of the operation 9 | EDF_EDbOperationStatusCode AddOrUpdate(notnull EDF_DbEntity entity) 10 | { 11 | if (!entity.HasId()) 12 | entity.SetId(EDF_DbEntityIdGenerator.Generate()); 13 | 14 | return m_Driver.AddOrUpdate(entity); 15 | } 16 | 17 | //------------------------------------------------------------------------------------------------ 18 | //! Remove an existing database entity 19 | //! \param entity database to remove 20 | //! \return status code of the operation, will fail if entity did not exist 21 | EDF_EDbOperationStatusCode Remove(notnull EDF_DbEntity entity) 22 | { 23 | // Save as vars because script vm invalid pointer bug if passed diretly 24 | typename type = entity.Type(); 25 | string id = entity.GetId(); 26 | return m_Driver.Remove(type, id); 27 | } 28 | 29 | //------------------------------------------------------------------------------------------------ 30 | //! Remove an existing database entity 31 | //! \param entityType typename of the database entity 32 | //! \param entityId unique id of the entity to remove 33 | //! \return status code of the operation, will fail if entity did not exist 34 | EDF_EDbOperationStatusCode Remove(typename entityType, string entityId) 35 | { 36 | return m_Driver.Remove(entityType, entityId); 37 | } 38 | 39 | //------------------------------------------------------------------------------------------------ 40 | //! Find database entities 41 | //! \param entityType typename of the database entity 42 | //! \param condition find condition to search by 43 | //! \param orderBy field paths in dotnotation to order by e.g. {{"child.subField", "ASC"}, {"thenByField", "DESC"}} 44 | //! \param limit maximum amount of returned. Limit is applied on those that matched the conditions 45 | //! \param offset used together with limit to offset the result limit count. Can be used to paginate the loading. 46 | //! \return find result buffer containing status code and result entities on success 47 | EDF_DbFindResultMultiple FindAll(typename entityType, EDF_DbFindCondition condition = null, array orderBy = null, int limit = -1, int offset = -1) 48 | { 49 | return m_Driver.FindAll(entityType, condition, orderBy, limit, offset); 50 | } 51 | 52 | //------------------------------------------------------------------------------------------------ 53 | //! Adds a new entry to the database or updates an existing one asynchronously 54 | //! \param entity database entity to add or update 55 | //! \param callback optional callback to handle the operation result 56 | void AddOrUpdateAsync(notnull EDF_DbEntity entity, EDF_DbOperationStatusOnlyCallback callback = null) 57 | { 58 | if (!entity.HasId()) 59 | entity.SetId(EDF_DbEntityIdGenerator.Generate()); 60 | 61 | m_Driver.AddOrUpdateAsync(entity, callback); 62 | } 63 | 64 | //------------------------------------------------------------------------------------------------ 65 | //! Remove an existing database entity asynchronously 66 | //! \param entity database to remove 67 | //! \param callback optional callback to handle the operation result 68 | void RemoveAsync(notnull EDF_DbEntity entity, EDF_DbOperationStatusOnlyCallback callback = null) 69 | { 70 | // Save as vars because script vm invalid pointer bug if passed diretly 71 | typename entityType = entity.Type(); 72 | string entityId = entity.GetId(); 73 | 74 | m_Driver.RemoveAsync(entityType, entityId, callback); 75 | } 76 | 77 | //------------------------------------------------------------------------------------------------ 78 | //! Remove an existing database entity asynchronously 79 | //! \param entityType typename of the database entity 80 | //! \param entityId unique id of the entity to remove 81 | //! \param callback optional callback to handle the operation result 82 | void RemoveAsync(typename entityType, string entityId, EDF_DbOperationStatusOnlyCallback callback = null) 83 | { 84 | m_Driver.RemoveAsync(entityType, entityId, callback); 85 | } 86 | 87 | //------------------------------------------------------------------------------------------------ 88 | //! Find database entities asynchronously 89 | //! \param entityType typename of the database entity 90 | //! \param condition find condition to search by 91 | //! \param orderBy field paths in dotnotation to order by e.g. {{"child.subField", "ASC"}, {"thenByField", "DESC"}} 92 | //! \param limit maximum amount of returned. Limit is applied on those that matched the conditions 93 | //! \param offset used together with limit to offset the result limit count. Can be used to paginate the loading. 94 | //! \param callback optional callback to handle the operation result 95 | void FindAllAsync(typename entityType, EDF_DbFindCondition condition = null, array orderBy = null, int limit = -1, int offset = -1, EDF_DbFindCallbackBase callback = null) 96 | { 97 | m_Driver.FindAllAsync(entityType, condition, orderBy, limit, offset, callback); 98 | } 99 | 100 | //------------------------------------------------------------------------------------------------ 101 | static EDF_DbContext Create(notnull EDF_DbConnectionInfoBase connectionInfo) 102 | { 103 | typename driverType = EDF_DbConnectionInfoDriverType.GetDriverType(connectionInfo.Type()); 104 | EDF_DbDriver driver = EDF_DbDriver.Cast(driverType.Spawn()); 105 | if (!driver || !driver.Initialize(connectionInfo)) 106 | { 107 | Debug.Error(string.Format("Unable to initialize database driver of type '%1'.", driverType)); 108 | return null; 109 | } 110 | 111 | return new EDF_DbContext(driver); 112 | } 113 | 114 | //------------------------------------------------------------------------------------------------ 115 | //! Use EDF_DbContext::Create(EDF_DbConnectionInfoBase) to get a context instance. 116 | protected void EDF_DbContext(notnull EDF_DbDriver driver) 117 | { 118 | m_Driver = driver; 119 | } 120 | 121 | //------------------------------------------------------------------------------------------------ 122 | void ~EDF_DbContext() 123 | { 124 | m_Driver = null; 125 | } 126 | }; 127 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_DbEntity.c: -------------------------------------------------------------------------------- 1 | class EDF_DbEntity 2 | { 3 | static const string FIELD_ID = "m_sId"; 4 | 5 | private string m_sId; 6 | 7 | //------------------------------------------------------------------------------------------------ 8 | //! Get the unique id of the db entity. Guaranteed to remain a string in any future version 9 | string GetId() 10 | { 11 | return m_sId; 12 | } 13 | 14 | //------------------------------------------------------------------------------------------------ 15 | //! Set the unique id on the db entity. Must always be a string! 16 | void SetId(string id) 17 | { 18 | m_sId = id; 19 | } 20 | 21 | //------------------------------------------------------------------------------------------------ 22 | //! Check if an id has been assigned 23 | bool HasId() 24 | { 25 | return m_sId; 26 | } 27 | 28 | //------------------------------------------------------------------------------------------------ 29 | //! Utility function to write id to serializer 30 | void WriteId(notnull BaseSerializationSaveContext saveContext) 31 | { 32 | saveContext.WriteValue(FIELD_ID, m_sId); 33 | } 34 | 35 | //------------------------------------------------------------------------------------------------ 36 | //! Utility function to read id from serializer 37 | void ReadId(notnull BaseSerializationLoadContext loadContext) 38 | { 39 | loadContext.ReadValue(FIELD_ID, m_sId); 40 | } 41 | }; 42 | 43 | class EDF_DbName 44 | { 45 | protected static const string AUTO_GENERATE = "AUTO_GENERATE"; 46 | 47 | protected static ref map s_mMapping; 48 | protected static ref map s_mReverseMapping; 49 | 50 | //------------------------------------------------------------------------------------------------ 51 | static string Get(typename entityType) 52 | { 53 | string name; 54 | if (!s_mMapping || !s_mMapping.Find(entityType, name)) 55 | name = entityType.ToString(); 56 | 57 | return name; 58 | } 59 | 60 | //------------------------------------------------------------------------------------------------ 61 | static typename GetTypeByName(string name) 62 | { 63 | typename resultType; 64 | if (!s_mReverseMapping || !s_mReverseMapping.Find(name, resultType)) 65 | resultType = name.ToType(); 66 | 67 | return resultType; 68 | } 69 | 70 | //------------------------------------------------------------------------------------------------ 71 | static EDF_DbName Automatic() 72 | { 73 | return new EDF_DbName(AUTO_GENERATE); 74 | } 75 | 76 | //------------------------------------------------------------------------------------------------ 77 | void EDF_DbName(string name = AUTO_GENERATE) 78 | { 79 | typename entityType = EDF_ReflectionUtils.GetAttributeParent(); 80 | 81 | if (!s_mMapping) 82 | s_mMapping = new map(); 83 | 84 | if (!s_mReverseMapping) 85 | s_mReverseMapping = new map(); 86 | 87 | if (name == AUTO_GENERATE) 88 | { 89 | name = entityType.ToString(); 90 | 91 | int tagIdx = name.IndexOf("_"); 92 | if (tagIdx != -1 && (tagIdx + 1) < name.Length()) 93 | name = name.Substring(tagIdx + 1, name.Length() - (tagIdx + 1)); 94 | 95 | if (name.StartsWith("Base")) 96 | name = name.Substring(4, name.Length() - 4); 97 | 98 | if (name.EndsWith("SaveData")) 99 | name = name.Substring(0, name.Length() - 8); 100 | 101 | s_mMapping.Set(entityType, name); 102 | s_mReverseMapping.Set(name, entityType); 103 | } 104 | 105 | s_mMapping.Set(entityType, name); 106 | s_mReverseMapping.Set(name, entityType); 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_DbEntityIdGenerator.c: -------------------------------------------------------------------------------- 1 | class EDF_DbEntityIdGenerator 2 | { 3 | protected static int s_iSequence; 4 | protected static ref RandomGenerator s_pRandom; 5 | 6 | //------------------------------------------------------------------------------------------------ 7 | static string Generate() 8 | { 9 | if (!s_pRandom) 10 | { 11 | s_pRandom = new RandomGenerator(); 12 | s_pRandom.SetSeed(System.GetUnixTime()); 13 | } 14 | 15 | //No need to look at past generated ids in db for conflict, because they have older timestamps 16 | string timeHex = EDF_HexHelper.Convert(System.GetUnixTime(), false, 8); 17 | 18 | //Sequence returns to 0 on overflow 19 | string seqHex = EDF_HexHelper.Convert(s_iSequence++, false, 8); 20 | if (s_iSequence == int.MAX) 21 | s_iSequence = 0; 22 | 23 | //Random component to ensure that even if all sequentials are generated in the same second it stays unique 24 | string randHex = EDF_HexHelper.Convert(s_pRandom.RandFloatXY(0, int.MAX), false, 8); 25 | string randHex2 = EDF_HexHelper.Convert(s_pRandom.RandFloatXY(0, int.MAX), false, 8); 26 | 27 | // TTTT TTTT-SEQ1-SEQ1-RND1-RND1 RND2 RND2 28 | return string.Format("%1-%2-%3-%4-%5%6", 29 | timeHex, 30 | seqHex.Substring(0, 4), 31 | seqHex.Substring(4, 4), 32 | randHex.Substring(0, 4), 33 | randHex.Substring(4, 4), 34 | randHex2); 35 | } 36 | 37 | //------------------------------------------------------------------------------------------------ 38 | static void Reset() 39 | { 40 | s_iSequence = 0; 41 | s_pRandom = null; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_DbEntitySorter.c: -------------------------------------------------------------------------------- 1 | class EDF_EDbEntitySortDirection 2 | { 3 | static const string ASCENDING = "ASC"; 4 | static const string DESCENDING = "DESC"; 5 | } 6 | 7 | class EDF_DbEntitySorter 8 | { 9 | //------------------------------------------------------------------------------------------------ 10 | static array GetSorted(notnull array entities, notnull array orderBy, int orderByIndex = 0) 11 | { 12 | if (entities.Count() < 2 || orderByIndex >= orderBy.Count()) return entities; 13 | 14 | string fieldName = orderBy.Get(orderByIndex).Get(0); 15 | array fieldSplits(); 16 | fieldName.Split(".", fieldSplits, true); 17 | 18 | // Empty sort condition, nothing to do 19 | if (fieldSplits.IsEmpty()) return entities; 20 | 21 | string sortDirection = orderBy.Get(orderByIndex).Get(1); 22 | sortDirection.ToLower(); 23 | bool descending = sortDirection == "desc"; 24 | 25 | //Collect all values for the field being sorted 26 | map> distinctValues(); 27 | 28 | //Get type from first instance, they should all be the same 29 | typename fieldType; 30 | 31 | //Add each entity to distinct map of field values 32 | foreach (EDF_DbEntity entity : entities) 33 | { 34 | string valueKey; 35 | GetSortValue(entity, fieldSplits, 0, valueKey, fieldType); 36 | 37 | array entityArray = distinctValues.Get(valueKey); 38 | if (!entityArray) 39 | { 40 | entityArray = new array(); 41 | distinctValues.Set(valueKey, entityArray); 42 | } 43 | 44 | entityArray.Insert(entity); 45 | } 46 | 47 | //Sort all field values according to input 48 | array valueKeysSorted(); 49 | valueKeysSorted.Reserve(distinctValues.Count()); 50 | 51 | // TODO: Use fixed lengh padded numbers to rely only on string sort without conversion 52 | switch(fieldType) 53 | { 54 | case int: 55 | { 56 | array valueKeysSortedTyped(); 57 | valueKeysSortedTyped.Reserve(distinctValues.Count()); 58 | 59 | //Parse strings back into sortable integers 60 | for (int nKey = 0, keys = distinctValues.Count(); nKey < keys; nKey++) 61 | { 62 | valueKeysSortedTyped.Insert(distinctValues.GetKey(nKey).ToInt()); 63 | } 64 | 65 | //Sort integers 66 | valueKeysSortedTyped.Sort(descending); 67 | 68 | //Turn back into strings to get objects from map 69 | foreach (int sortedKey : valueKeysSortedTyped) 70 | { 71 | valueKeysSorted.Insert(sortedKey.ToString()); 72 | } 73 | 74 | break; 75 | } 76 | 77 | case float: 78 | { 79 | array valueKeysSortedTyped(); 80 | valueKeysSortedTyped.Reserve(distinctValues.Count()); 81 | 82 | //Parse strings back into sortable floats 83 | for (int nKey = 0, keys = distinctValues.Count(); nKey < keys; nKey++) 84 | { 85 | valueKeysSortedTyped.Insert(distinctValues.GetKey(nKey).ToFloat()); 86 | } 87 | 88 | //Sort floats 89 | valueKeysSortedTyped.Sort(descending); 90 | 91 | //Turn back into strings to get objects from map 92 | foreach (int index, float sortedKey : valueKeysSortedTyped) 93 | { 94 | valueKeysSorted.Insert(sortedKey.ToString()); 95 | } 96 | 97 | break; 98 | } 99 | 100 | case bool: 101 | { 102 | array valueKeysSortedTyped(); 103 | valueKeysSortedTyped.Reserve(distinctValues.Count()); 104 | 105 | //Parse strings back into sortable booleans 106 | for (int nKey = 0, keys = distinctValues.Count(); nKey < keys; nKey++) 107 | { 108 | valueKeysSortedTyped.Insert(distinctValues.GetKey(nKey) == "true"); 109 | } 110 | 111 | //Sort booleans 112 | valueKeysSortedTyped.Sort(descending); 113 | 114 | //Turn back into strings to get objects from map 115 | foreach (int index, bool sortedKey : valueKeysSortedTyped) 116 | { 117 | valueKeysSorted.Insert(sortedKey.ToString()); 118 | } 119 | 120 | break; 121 | } 122 | 123 | case string: 124 | { 125 | for (int nKey = 0, keys = distinctValues.Count(); nKey < keys; nKey++) 126 | { 127 | valueKeysSorted.Insert(distinctValues.GetKey(nKey)); 128 | } 129 | 130 | valueKeysSorted.Sort(descending); 131 | 132 | break; 133 | } 134 | 135 | case vector: 136 | { 137 | array valueKeysSortedTyped(); 138 | valueKeysSortedTyped.Reserve(distinctValues.Count()); 139 | 140 | //Parse strings back into sortable booleans 141 | for (int nKey = 0, keys = distinctValues.Count(); nKey < keys; nKey++) 142 | { 143 | valueKeysSortedTyped.Insert(distinctValues.GetKey(nKey).ToVector()); 144 | } 145 | 146 | //Sort booleans 147 | valueKeysSortedTyped.Sort(descending); 148 | 149 | //Turn back into strings to get objects from map 150 | foreach (int index, vector sortedKey : valueKeysSortedTyped) 151 | { 152 | valueKeysSorted.Insert(sortedKey.ToString(false)); 153 | } 154 | 155 | break; 156 | } 157 | } 158 | 159 | array sortedEnties(); 160 | sortedEnties.Reserve(entities.Count()); 161 | 162 | foreach (string sortedValueKey : valueKeysSorted) 163 | { 164 | array sameKeyEntities = distinctValues.Get(sortedValueKey); 165 | 166 | if (sameKeyEntities.Count() == 1) 167 | { 168 | sortedEnties.Insert(sameKeyEntities.Get(0)); 169 | } 170 | else 171 | { 172 | array subSortedEnities = GetSorted(sameKeyEntities, orderBy, orderByIndex + 1); 173 | 174 | foreach (EDF_DbEntity subSortedEntity : subSortedEnities) 175 | { 176 | sortedEnties.Insert(subSortedEntity); 177 | } 178 | } 179 | } 180 | 181 | return sortedEnties; 182 | } 183 | 184 | //------------------------------------------------------------------------------------------------ 185 | protected static void GetSortValue(Class instance, array fieldSplits, int currentIndex, out string sortValue, out typename valueType) 186 | { 187 | string currentFieldName = fieldSplits.Get(currentIndex); 188 | EDF_ReflectionVariableInfo variableinfo = EDF_ReflectionVariableInfo.Get(instance, currentFieldName); 189 | valueType = variableinfo.m_tVaribleType; 190 | 191 | // Expand nested object 192 | if (currentIndex < fieldSplits.Count() - 1) 193 | { 194 | if (valueType.IsInherited(array) || valueType.IsInherited(set) || valueType.IsInherited(map)) 195 | { 196 | Debug.Error(string.Format("Can not get sort value from collection type '%1' on '%2.%3'", valueType, variableinfo.m_tHolderType, currentFieldName)); 197 | return; 198 | } 199 | else if (!valueType.IsInherited(Class)) 200 | { 201 | Debug.Error(string.Format("Can not expand primitive type '%1' on '%2.%3' to read field '%4'", valueType, variableinfo.m_tHolderType, currentFieldName, fieldSplits.Get(currentIndex + 1))); 202 | return; 203 | } 204 | 205 | Class complexHolder; 206 | if (!variableinfo.m_tHolderType.GetVariableValue(instance, variableinfo.m_iVariableIndex, complexHolder)) return; 207 | GetSortValue(complexHolder, fieldSplits, currentIndex + 1, sortValue, valueType); 208 | } 209 | 210 | // Read primitive field value and convert to compareable 211 | switch(valueType) 212 | { 213 | case int: 214 | { 215 | int outVal; 216 | if (!variableinfo.m_tHolderType.GetVariableValue(instance, variableinfo.m_iVariableIndex, outVal)) return; 217 | sortValue = outVal.ToString(); 218 | return; 219 | } 220 | 221 | case float: 222 | { 223 | float outVal; 224 | if (!variableinfo.m_tHolderType.GetVariableValue(instance, variableinfo.m_iVariableIndex, outVal)) return; 225 | sortValue = outVal.ToString(); 226 | return; 227 | } 228 | 229 | case bool: 230 | { 231 | bool outVal; 232 | if (!variableinfo.m_tHolderType.GetVariableValue(instance, variableinfo.m_iVariableIndex, outVal)) return; 233 | sortValue = outVal.ToString(); 234 | return; 235 | } 236 | 237 | case string: 238 | { 239 | if (!variableinfo.m_tHolderType.GetVariableValue(instance, variableinfo.m_iVariableIndex, sortValue)) return; 240 | return; 241 | } 242 | 243 | case vector: 244 | { 245 | vector outVal; 246 | if (!variableinfo.m_tHolderType.GetVariableValue(instance, variableinfo.m_iVariableIndex, outVal)) return; 247 | sortValue = outVal.ToString(false); 248 | return; 249 | } 250 | } 251 | 252 | Debug.Error(string.Format("Can not sort entity collection by field '%1' with non sortable type '%2'.", currentFieldName, valueType)); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_DbEntityUtils.c: -------------------------------------------------------------------------------- 1 | class EDF_DbEntityUtils 2 | { 3 | //------------------------------------------------------------------------------------------------ 4 | //! Memeber-wise deep copy of data from source to matching named instance variables on the destination 5 | static bool StructAutoCopy(notnull Managed source, notnull Managed destination) 6 | { 7 | SCR_JsonSaveContext writer(); 8 | if (!writer.WriteValue("", source)) 9 | return false; 10 | 11 | string data = writer.ExportToString(); 12 | 13 | SCR_JsonLoadContext reader(); 14 | if (!reader.ImportFromString(data)) 15 | return false; 16 | 17 | return reader.ReadValue("", destination); 18 | } 19 | 20 | //------------------------------------------------------------------------------------------------ 21 | static Managed DeepCopy(notnull Managed instance) 22 | { 23 | SCR_BinSaveContext writer(); 24 | if (!writer.WriteValue("", instance)) 25 | return null; 26 | 27 | SCR_BinLoadContext reader(); 28 | if (!reader.LoadFromContainer(writer.SaveToContainer())) 29 | return null; 30 | 31 | auto deepCopy = instance.Type().Spawn(); 32 | if (!reader.ReadValue("", deepCopy)) 33 | return null; 34 | 35 | return deepCopy; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_DbOperationResultTypes.c: -------------------------------------------------------------------------------- 1 | enum EDF_EDbOperationStatusCode 2 | { 3 | SUCCESS, 4 | 5 | // Framework failure 6 | FAILURE_NOT_IMPLEMENTED, 7 | 8 | // Database failure 9 | FAILURE_DB_UNAVAILABLE, 10 | FAILURE_RESPONSE_MALFORMED, 11 | 12 | // User failure 13 | FAILURE_DATA_MALFORMED, 14 | FAILURE_ID_NOT_SET, 15 | FAILURE_ID_NOT_FOUND, 16 | 17 | // Unknown 18 | FAILURE_UNKNOWN 19 | }; 20 | 21 | class EDF_DbFindResultBase 22 | { 23 | protected EDF_EDbOperationStatusCode m_eStatusCode; 24 | 25 | //------------------------------------------------------------------------------------------------ 26 | EDF_EDbOperationStatusCode GetStatusCode() 27 | { 28 | return m_eStatusCode; 29 | } 30 | 31 | //------------------------------------------------------------------------------------------------ 32 | bool IsSuccess() 33 | { 34 | return m_eStatusCode == EDF_EDbOperationStatusCode.SUCCESS; 35 | } 36 | }; 37 | 38 | class EDF_DbFindResultMultiple : EDF_DbFindResultBase 39 | { 40 | protected ref array m_aEntities; 41 | 42 | //------------------------------------------------------------------------------------------------ 43 | array GetEntities() 44 | { 45 | return m_aEntities; 46 | } 47 | 48 | //------------------------------------------------------------------------------------------------ 49 | void EDF_DbFindResultMultiple(EDF_EDbOperationStatusCode statusCode, array entities = null) 50 | { 51 | m_eStatusCode = statusCode; 52 | m_aEntities = entities; 53 | } 54 | }; 55 | 56 | class EDF_DbFindResultSingle : EDF_DbFindResultBase 57 | { 58 | protected ref TEntityType m_pEntity; 59 | 60 | //------------------------------------------------------------------------------------------------ 61 | TEntityType GetEntity() 62 | { 63 | return m_pEntity; 64 | } 65 | 66 | //------------------------------------------------------------------------------------------------ 67 | void EDF_DbFindResultSingle(EDF_EDbOperationStatusCode statusCode, TEntityType entity = null) 68 | { 69 | m_eStatusCode = statusCode; 70 | m_pEntity = entity; 71 | } 72 | }; 73 | 74 | class EDF_DbOperationCallback : EDF_Callback 75 | { 76 | }; 77 | 78 | class EDF_DbOperationStatusOnlyCallback : EDF_DbOperationCallback 79 | { 80 | //------------------------------------------------------------------------------------------------ 81 | void OnSuccess(Managed context); 82 | 83 | //------------------------------------------------------------------------------------------------ 84 | void OnFailure(EDF_EDbOperationStatusCode statusCode, Managed context); 85 | 86 | //------------------------------------------------------------------------------------------------ 87 | sealed void Invoke(EDF_EDbOperationStatusCode code) 88 | { 89 | if (m_pInvokeInstance && 90 | m_sInvokeMethod && 91 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, code, m_pContext)) return; 92 | 93 | if (code == EDF_EDbOperationStatusCode.SUCCESS) 94 | { 95 | OnSuccess(m_pContext); 96 | } 97 | else 98 | { 99 | OnFailure(code, m_pContext); 100 | } 101 | } 102 | }; 103 | 104 | class EDF_DbFindCallbackBase : EDF_DbOperationCallback 105 | { 106 | //------------------------------------------------------------------------------------------------ 107 | void Invoke(EDF_EDbOperationStatusCode code, array findResults); 108 | }; 109 | 110 | class EDF_DbFindCallbackMultipleUntyped : EDF_DbFindCallbackBase 111 | { 112 | //------------------------------------------------------------------------------------------------ 113 | void OnSuccess(array results, Managed context); 114 | 115 | //------------------------------------------------------------------------------------------------ 116 | void OnFailure(EDF_EDbOperationStatusCode statusCode, Managed context); 117 | 118 | //------------------------------------------------------------------------------------------------ 119 | sealed override void Invoke(EDF_EDbOperationStatusCode code, array findResults) 120 | { 121 | if (m_pInvokeInstance && 122 | m_sInvokeMethod && 123 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, code, findResults, m_pContext)) return; 124 | 125 | if (code == EDF_EDbOperationStatusCode.SUCCESS) 126 | { 127 | OnSuccess(findResults, m_pContext); 128 | } 129 | else 130 | { 131 | OnFailure(code, m_pContext); 132 | } 133 | } 134 | }; 135 | 136 | class EDF_DbFindCallbackMultiple : EDF_DbFindCallbackBase 137 | { 138 | //------------------------------------------------------------------------------------------------ 139 | void OnSuccess(array results, Managed context); 140 | 141 | //------------------------------------------------------------------------------------------------ 142 | void OnFailure(EDF_EDbOperationStatusCode statusCode, Managed context); 143 | 144 | //------------------------------------------------------------------------------------------------ 145 | sealed override void Invoke(EDF_EDbOperationStatusCode code, array findResults) 146 | { 147 | array strongTypedResults = EDF_RefArrayCaster.Convert(findResults); 148 | 149 | if (m_pInvokeInstance && 150 | m_sInvokeMethod && 151 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, code, strongTypedResults, m_pContext)) return; 152 | 153 | if (code == EDF_EDbOperationStatusCode.SUCCESS) 154 | { 155 | OnSuccess(strongTypedResults, m_pContext); 156 | } 157 | else 158 | { 159 | OnFailure(code, m_pContext); 160 | } 161 | } 162 | }; 163 | 164 | class EDF_DbFindCallbackSingle : EDF_DbFindCallbackBase 165 | { 166 | //------------------------------------------------------------------------------------------------ 167 | void OnSuccess(TEntityType result, Managed context); 168 | 169 | //------------------------------------------------------------------------------------------------ 170 | void OnFailure(EDF_EDbOperationStatusCode statusCode, Managed context); 171 | 172 | //------------------------------------------------------------------------------------------------ 173 | sealed override void Invoke(EDF_EDbOperationStatusCode code, array findResults) 174 | { 175 | TEntityType typedResult; 176 | 177 | if (findResults.Count() > 0) 178 | typedResult = TEntityType.Cast(findResults.Get(0)); 179 | 180 | if (m_pInvokeInstance && 181 | m_sInvokeMethod && 182 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, code, typedResult, m_pContext)) return; 183 | 184 | if (code == EDF_EDbOperationStatusCode.SUCCESS) 185 | { 186 | OnSuccess(typedResult, m_pContext); 187 | } 188 | else 189 | { 190 | OnFailure(code, m_pContext); 191 | } 192 | } 193 | }; 194 | 195 | class EDF_DbFindCallbackSingleton : EDF_DbFindCallbackBase 196 | { 197 | //------------------------------------------------------------------------------------------------ 198 | void OnSuccess(TEntityType result, Managed context); 199 | 200 | //------------------------------------------------------------------------------------------------ 201 | void OnFailure(EDF_EDbOperationStatusCode statusCode, Managed context); 202 | 203 | //------------------------------------------------------------------------------------------------ 204 | sealed override void Invoke(EDF_EDbOperationStatusCode code, array findResults) 205 | { 206 | TEntityType typedResult; 207 | 208 | if (findResults.Count() > 0) 209 | { 210 | typedResult = TEntityType.Cast(findResults.Get(0)); 211 | } 212 | else 213 | { 214 | typename spawnType = TEntityType; 215 | typedResult = TEntityType.Cast(spawnType.Spawn()); 216 | } 217 | 218 | if (m_pInvokeInstance && 219 | m_sInvokeMethod && 220 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, true, false, code, typedResult, m_pContext)) return; 221 | 222 | if (code == EDF_EDbOperationStatusCode.SUCCESS) 223 | { 224 | OnSuccess(typedResult, m_pContext); 225 | } 226 | else 227 | { 228 | OnFailure(code, m_pContext); 229 | } 230 | } 231 | }; 232 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_DbRepository.c: -------------------------------------------------------------------------------- 1 | class EDF_DbRepositoryBase 2 | { 3 | protected ref EDF_DbContext m_DbContext; 4 | 5 | //------------------------------------------------------------------------------------------------ 6 | EDF_DbContext GetDbContext() 7 | { 8 | return m_DbContext; 9 | } 10 | 11 | //------------------------------------------------------------------------------------------------ 12 | //! Use with caution, normally you should never need to use this! 13 | void SetDbContext(EDF_DbContext dbContext) 14 | { 15 | m_DbContext = dbContext; 16 | } 17 | 18 | //------------------------------------------------------------------------------------------------ 19 | //! Creation only through EDF_DbRepositoryFactory::GetRepository 20 | /*protected void EDF_DbRepositoryBase(); --Hotfix for 1.0*/ 21 | }; 22 | 23 | class EDF_DbRepository : EDF_DbRepositoryBase 24 | { 25 | // ------------------------------------ Sync API ------------------------------------------------- 26 | 27 | //------------------------------------------------------------------------------------------------ 28 | EDF_EDbOperationStatusCode AddOrUpdate(notnull TEntityType entity) 29 | { 30 | return m_DbContext.AddOrUpdate(entity); 31 | } 32 | 33 | //------------------------------------------------------------------------------------------------ 34 | EDF_EDbOperationStatusCode Remove(string entityId) 35 | { 36 | return m_DbContext.Remove(TEntityType, entityId); 37 | } 38 | 39 | //------------------------------------------------------------------------------------------------ 40 | EDF_EDbOperationStatusCode Remove(notnull TEntityType entity) 41 | { 42 | return m_DbContext.Remove(TEntityType, entity.GetId()); 43 | } 44 | 45 | //------------------------------------------------------------------------------------------------ 46 | EDF_DbFindResultSingle Find(string entityId) 47 | { 48 | return FindFirst(EDF_DbFind.Id().Equals(entityId)); 49 | } 50 | 51 | //------------------------------------------------------------------------------------------------ 52 | EDF_DbFindResultSingle FindSingleton() 53 | { 54 | EDF_DbFindResultSingle findResult = FindFirst(); 55 | 56 | if (!findResult.IsSuccess() || findResult.GetEntity()) 57 | return findResult; 58 | 59 | typename spawnType = TEntityType; 60 | return new EDF_DbFindResultSingle(EDF_EDbOperationStatusCode.SUCCESS, TEntityType.Cast(spawnType.Spawn())); 61 | } 62 | 63 | //------------------------------------------------------------------------------------------------ 64 | EDF_DbFindResultSingle FindFirst(EDF_DbFindCondition condition = null, array> orderBy = null) 65 | { 66 | EDF_DbFindResultMultiple findResults = m_DbContext.FindAll(TEntityType, condition, orderBy, 1); 67 | 68 | if (!findResults.IsSuccess()) 69 | return new EDF_DbFindResultSingle(findResults.GetStatusCode()); 70 | 71 | TEntityType entity = null; 72 | if (findResults.GetEntities().Count() > 0) 73 | entity = TEntityType.Cast(findResults.GetEntities().Get(0)); 74 | 75 | return new EDF_DbFindResultSingle(findResults.GetStatusCode(), entity); 76 | } 77 | 78 | //------------------------------------------------------------------------------------------------ 79 | EDF_DbFindResultMultiple FindAll(EDF_DbFindCondition condition = null, array> orderBy = null, int limit = -1, int offset = -1) 80 | { 81 | EDF_DbFindResultMultiple findResults = m_DbContext.FindAll(TEntityType, condition, orderBy, limit, offset); 82 | return new EDF_DbFindResultMultiple(findResults.GetStatusCode(), EDF_RefArrayCaster.Convert(findResults.GetEntities())); 83 | } 84 | 85 | // ------------------------------------------ ASYNC API ------------------------------------------ 86 | 87 | //------------------------------------------------------------------------------------------------ 88 | void AddOrUpdateAsync(notnull TEntityType entity, EDF_DbOperationStatusOnlyCallback callback = null) 89 | { 90 | m_DbContext.AddOrUpdateAsync(entity, callback); 91 | } 92 | 93 | //------------------------------------------------------------------------------------------------ 94 | void RemoveAsync(string entityId, EDF_DbOperationStatusOnlyCallback callback = null) 95 | { 96 | m_DbContext.RemoveAsync(TEntityType, entityId, callback); 97 | } 98 | 99 | //------------------------------------------------------------------------------------------------ 100 | void RemoveAsync(TEntityType entity, EDF_DbOperationStatusOnlyCallback callback = null) 101 | { 102 | m_DbContext.RemoveAsync(TEntityType, entity.GetId(), callback); 103 | } 104 | 105 | //------------------------------------------------------------------------------------------------ 106 | void FindAsync(string entityId, notnull EDF_DbFindCallbackSingle callback) 107 | { 108 | m_DbContext.FindAllAsync(TEntityType, EDF_DbFind.Id().Equals(entityId), null, 1, -1, callback); 109 | } 110 | 111 | //------------------------------------------------------------------------------------------------ 112 | void FindSingletonAsync(notnull EDF_DbFindCallbackSingleton callback) 113 | { 114 | m_DbContext.FindAllAsync(TEntityType, null, null, 1, -1, callback); 115 | } 116 | 117 | //------------------------------------------------------------------------------------------------ 118 | void FindFirstAsync(EDF_DbFindCondition condition = null, array> orderBy = null, EDF_DbFindCallbackSingle callback = null) 119 | { 120 | m_DbContext.FindAllAsync(TEntityType, condition, orderBy, 1, -1, callback); 121 | } 122 | 123 | //------------------------------------------------------------------------------------------------ 124 | void FindAllAsync(EDF_DbFindCondition condition = null, array> orderBy = null, int limit = -1, int offset = -1, notnull EDF_DbFindCallbackMultiple callback = null) 125 | { 126 | m_DbContext.FindAllAsync(TEntityType, condition, orderBy, limit, offset, callback); 127 | } 128 | 129 | //------------------------------------------------------------------------------------------------ 130 | /*sealed*/ static typename GetEntityType() 131 | { 132 | return TEntityType; 133 | } 134 | }; 135 | 136 | class EDF_DbRepositoryRegistration 137 | { 138 | protected static ref map s_mMapping; 139 | protected static ref array s_aRegistrationQueue; 140 | 141 | //------------------------------------------------------------------------------------------------ 142 | static typename Get(typename entityType) 143 | { 144 | if (!s_mMapping) 145 | s_mMapping = new map(); 146 | 147 | typename result = s_mMapping.Get(entityType); 148 | if (!result) 149 | { 150 | string repositoryTypeStr = string.Format("EDF_DbRepository<%1>", entityType.ToString()); 151 | 152 | result = repositoryTypeStr.ToType(); 153 | 154 | if (result) 155 | { 156 | // Save default implementation repository to cache 157 | s_mMapping.Set(entityType, result); 158 | } 159 | } 160 | 161 | return result; 162 | } 163 | 164 | //------------------------------------------------------------------------------------------------ 165 | static void FlushRegistrations(ScriptModule scriptModule) 166 | { 167 | if (!s_aRegistrationQueue || !scriptModule) 168 | return; 169 | 170 | if (!s_mMapping) 171 | s_mMapping = new map(); 172 | 173 | foreach (typename repositoryType : s_aRegistrationQueue) 174 | { 175 | auto reflectionInst = repositoryType.Spawn(); 176 | if (!reflectionInst) 177 | continue; 178 | 179 | typename entityType; 180 | scriptModule.Call(reflectionInst, "GetEntityType", false, entityType); 181 | reflectionInst = null; 182 | 183 | typename expectedBase = string.Format("EDF_DbRepository<%1>", entityType.ToString()).ToType(); 184 | if (!repositoryType.IsInherited(expectedBase)) 185 | { 186 | Debug.Error(string.Format("Failed to register '%1' as repository for '%2'. '%1' must inherit from '%3'.", repositoryType, entityType, expectedBase)); 187 | continue; 188 | } 189 | 190 | if (!entityType.IsInherited(EDF_DbEntity)) 191 | { 192 | Debug.Error(string.Format("Db repository '%1' created with entity type '%2' that does not inherit from '%3'. Results will be invalid!", repositoryType, entityType, EDF_DbEntity)); 193 | continue; 194 | } 195 | 196 | s_mMapping.Set(entityType, repositoryType); 197 | } 198 | } 199 | 200 | //------------------------------------------------------------------------------------------------ 201 | static void EDF_DbRepositoryRegistration() 202 | { 203 | // TODO: Retrire registration attribute when we can can get all inherited types from our repository base classes 204 | 205 | typename repositoryType = EDF_ReflectionUtils.GetAttributeParent(); 206 | 207 | if (!s_aRegistrationQueue) 208 | { 209 | s_aRegistrationQueue = {repositoryType}; 210 | return; 211 | } 212 | 213 | s_aRegistrationQueue.Insert(repositoryType); 214 | } 215 | }; 216 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_DbRepositoryFactory.c: -------------------------------------------------------------------------------- 1 | /*sealed*/ class EDF_DbRepositoryFactory 2 | { 3 | private static ref map s_mRepositoryCache; 4 | 5 | //------------------------------------------------------------------------------------------------ 6 | static EDF_DbRepositoryBase GetRepository(typename repositoryType, notnull EDF_DbContext dbContext) 7 | { 8 | EDF_DbRepositoryBase repository = null; 9 | 10 | if (!s_mRepositoryCache) 11 | s_mRepositoryCache = new map(); 12 | 13 | string cacheKey = string.Format("%1:%2", repositoryType.ToString(), dbContext); 14 | 15 | repository = s_mRepositoryCache.Get(cacheKey); 16 | 17 | // No valid repository found, create a new one 18 | if (!repository) 19 | { 20 | repository = EDF_DbRepositoryBase.Cast(repositoryType.Spawn()); 21 | 22 | if (repository) 23 | repository.SetDbContext(dbContext); 24 | } 25 | 26 | // Cache repository to be re-used, even if null because second time it would still create an invalid one 27 | s_mRepositoryCache.Set(cacheKey, repository); 28 | 29 | return repository; 30 | } 31 | 32 | //------------------------------------------------------------------------------------------------ 33 | static void ResetCache() 34 | { 35 | s_mRepositoryCache = null; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_DbRepositoryHelper.c: -------------------------------------------------------------------------------- 1 | class EDF_DbEntityHelper 2 | { 3 | //------------------------------------------------------------------------------------------------ 4 | //! Get the repository responsible to handling the templated entity type 5 | //! \param dbContext Database context to wrap with the repository 6 | //! \return database repository instance or null if no type could be found. 7 | static EDF_DbRepository GetRepository(notnull EDF_DbContext dbContext) 8 | { 9 | typename repositoryType = EDF_DbRepositoryRegistration.Get(TEntityType); // Can not be inlined or else illegal read happens because of bug in scriptvm. 10 | if (!repositoryType) 11 | { 12 | string repositoryTypeStr = string.Format("EDF_DbRepository<%1>", TEntityType); 13 | Debug.Error(string.Format("Tried to get unknown entity repository type '%1'. Make sure you use it somewhere in your code e.g.: '%1 repository = ...;'", repositoryTypeStr)); 14 | } 15 | 16 | return EDF_DbRepository.Cast(EDF_DbRepositoryFactory.GetRepository(repositoryType, dbContext)); 17 | } 18 | }; 19 | 20 | class EDF_DbRepositoryHelper 21 | { 22 | //------------------------------------------------------------------------------------------------ 23 | static TRepositoryType Get(notnull EDF_DbContext dbContext) 24 | { 25 | return TRepositoryType.Cast(EDF_DbRepositoryFactory.GetRepository(TRepositoryType, dbContext)); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_HexHelper.c: -------------------------------------------------------------------------------- 1 | class EDF_HexHelper 2 | { 3 | //------------------------------------------------------------------------------------------------ 4 | //! Convert integer to hexadeciamal string 5 | //! \param value Input integer 6 | //! \param upperCase Decide if output should be upper or lower case 7 | //! \param fixedLength Add leading zeros for a minimum length output 8 | //! \return result hexadecimal string 9 | static string Convert(int value, bool upperCase = false, int fixedLength = -1) 10 | { 11 | array resultChars = {"0", "0", "0", "0", "0", "0", "0", "0"}; 12 | 13 | int asciiOffset = 87; 14 | if (upperCase) asciiOffset = 55; 15 | 16 | int padUntil = 7; 17 | if (fixedLength != -1) padUntil = 8 - Math.Min(fixedLength, 8); 18 | 19 | int resultIdx = 7; 20 | 21 | while (value) 22 | { 23 | int remainder = value % 16; 24 | 25 | if (remainder < 10) 26 | { 27 | resultChars.Set(resultIdx--, remainder.ToString()); 28 | } 29 | else 30 | { 31 | resultChars.Set(resultIdx--, (remainder + asciiOffset).AsciiToString()); 32 | } 33 | 34 | value /= 16; 35 | } 36 | 37 | string result; 38 | bool nonZero; 39 | 40 | foreach (int nChar, string char : resultChars) 41 | { 42 | if (char == "0" && nChar < padUntil && !nonZero) continue; 43 | nonZero = true; 44 | result += char; 45 | } 46 | 47 | return result; 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_RefArrayCaster.c: -------------------------------------------------------------------------------- 1 | class EDF_RefArrayCaster 2 | { 3 | //------------------------------------------------------------------------------------------------ 4 | //! Cast an array by converting all individual items. Allocates a new array of the input size. 5 | //! \param sourceArray Input array 6 | //! \return casted result array 7 | static array Convert(array sourceArray) 8 | { 9 | if (!sourceArray) 10 | return null; 11 | 12 | array castedResult(); 13 | castedResult.Reserve(sourceArray.Count()); 14 | 15 | foreach (TSourceType element : sourceArray) 16 | { 17 | TResultType castedElement = TResultType.Cast(element); 18 | if (castedElement) 19 | castedResult.Insert(castedElement); 20 | } 21 | 22 | return castedResult; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_ReflectionUtils.c: -------------------------------------------------------------------------------- 1 | class EDF_ReflectionUtils 2 | { 3 | static typename GetAttributeParent() 4 | { 5 | string callStack; 6 | Debug.DumpStack(callStack); 7 | int startRead = callStack.LastIndexOf("#CreateAttributes") + 17; 8 | int stopRead = callStack.IndexOfFrom(startRead, "("); 9 | return callStack.Substring(startRead, stopRead - startRead).ToType(); 10 | } 11 | }; 12 | 13 | class EDF_ReflectionUtilsT 14 | { 15 | //------------------------------------------------------------------------------------------------ 16 | static bool Get(notnull Class instance, string variableName, out T value) 17 | { 18 | EDF_ReflectionVariableInfo info = EDF_ReflectionVariableInfo.Get(instance, variableName); 19 | if (!info) 20 | return false; 21 | return info.m_tHolderType.GetVariableValue(instance, info.m_iVariableIndex, value); 22 | } 23 | 24 | //------------------------------------------------------------------------------------------------ 25 | //TODO: Add Set() via Json serializer 26 | }; 27 | 28 | enum EDF_ReflectionVariableCollectionType 29 | { 30 | NONE, 31 | ARRAY, 32 | SET, 33 | MAP 34 | }; 35 | 36 | class EDF_ReflectionVariableInfo 37 | { 38 | protected static ref map s_mTypeCache; 39 | 40 | int m_iVariableIndex; 41 | typename m_tVaribleType; 42 | typename m_tHolderType; 43 | typename m_tCollectionKeyType; 44 | typename m_tCollectionValueType; 45 | EDF_ReflectionVariableCollectionType m_eCollectionType; 46 | 47 | //------------------------------------------------------------------------------------------------ 48 | //! Empty fieldname indicates nested collection 49 | static EDF_ReflectionVariableInfo Get(notnull Class instance, string fieldName) 50 | { 51 | if (!s_mTypeCache) 52 | s_mTypeCache = new map(); 53 | 54 | typename type = instance.Type(); 55 | string typeCacheKey = string.Format("%1::%2", type.ToString(), fieldName); 56 | EDF_ReflectionVariableInfo info = s_mTypeCache.Get(typeCacheKey); 57 | if (!info) 58 | { 59 | int variableIndex = -1; 60 | typename varibleType; 61 | typename holderType; 62 | 63 | if (fieldName) 64 | { 65 | holderType = type; 66 | for (int vIdx = 0; vIdx < type.GetVariableCount(); vIdx++) 67 | { 68 | if (type.GetVariableName(vIdx) == fieldName) 69 | { 70 | variableIndex = vIdx; 71 | varibleType = type.GetVariableType(vIdx); 72 | break; 73 | } 74 | } 75 | } 76 | else 77 | { 78 | varibleType = type; 79 | } 80 | 81 | info = new EDF_ReflectionVariableInfo(variableIndex, varibleType, holderType); 82 | s_mTypeCache.Set(typeCacheKey, info); 83 | } 84 | 85 | return info; 86 | } 87 | 88 | //------------------------------------------------------------------------------------------------ 89 | protected void EDF_ReflectionVariableInfo(int variableIndex, typename variableType, typename holderType) 90 | { 91 | m_iVariableIndex = variableIndex; 92 | m_tVaribleType = variableType; 93 | m_tHolderType = holderType; 94 | 95 | if (variableType.IsInherited(array) || variableType.IsInherited(set)) 96 | { 97 | if (variableType.IsInherited(array)) 98 | { 99 | m_eCollectionType = EDF_ReflectionVariableCollectionType.ARRAY; 100 | } 101 | else 102 | { 103 | m_eCollectionType = EDF_ReflectionVariableCollectionType.SET; 104 | } 105 | 106 | string typeString = variableType.ToString(); 107 | typeString.Replace("@", ""); 108 | 109 | int templateStart = typeString.IndexOf("<") + 1; 110 | string collectionValueTypeString = typeString.Substring(templateStart, typeString.Length() - templateStart - 1); 111 | m_tCollectionValueType = collectionValueTypeString.ToType(); 112 | } 113 | else if (variableType.IsInherited(map)) 114 | { 115 | m_eCollectionType = EDF_ReflectionVariableCollectionType.MAP; 116 | 117 | string typeString = variableType.ToString(); 118 | typeString.Replace("@", ""); 119 | 120 | int keyTypeStart = typeString.IndexOf("<") + 1; 121 | int valueTypeStart = typeString.IndexOfFrom(keyTypeStart, ",") + 1; 122 | 123 | string collectionKeyTypeString = typeString.Substring(keyTypeStart, valueTypeStart - keyTypeStart - 1); 124 | m_tCollectionKeyType = collectionKeyTypeString.ToType(); 125 | 126 | string collectionValueTypeString = typeString.Substring(valueTypeStart, typeString.Length() - valueTypeStart - 1); 127 | m_tCollectionValueType = collectionValueTypeString.ToType(); 128 | } 129 | } 130 | }; 131 | -------------------------------------------------------------------------------- /src/Scripts/Game/EDF_ScriptInvokerCallback.c: -------------------------------------------------------------------------------- 1 | // Needs to have base class without the invoke method otherwise script vm thinks that "callbacks do not support overloaded methods" ... 2 | class EDF_ScriptInvokerCallbackBase 3 | { 4 | protected static ref set s_CallbackReferences = new set(); 5 | 6 | ref Managed m_pContext; 7 | Class m_pInvokeInstance; 8 | string m_sInvokeMethod; 9 | 10 | //------------------------------------------------------------------------------------------------ 11 | void EDF_ScriptInvokerCallbackBase(Class invokeInstance, string invokeMethod, Managed context = null) 12 | { 13 | s_CallbackReferences.Insert(this); 14 | m_pInvokeInstance = invokeInstance; 15 | m_sInvokeMethod = invokeMethod; 16 | m_pContext = context; 17 | } 18 | }; 19 | 20 | class EDF_ScriptInvokerCallback : EDF_ScriptInvokerCallbackBase 21 | { 22 | //------------------------------------------------------------------------------------------------ 23 | sealed void Invoke() 24 | { 25 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, m_pContext); 26 | s_CallbackReferences.RemoveItem(this); 27 | } 28 | }; 29 | 30 | class EDF_ScriptInvokerCallback1 : EDF_ScriptInvokerCallbackBase 31 | { 32 | //------------------------------------------------------------------------------------------------ 33 | sealed void Invoke(T1 t1) 34 | { 35 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, t1, m_pContext); 36 | s_CallbackReferences.RemoveItem(this); 37 | } 38 | }; 39 | 40 | class EDF_ScriptInvokerCallback2 : EDF_ScriptInvokerCallbackBase 41 | { 42 | //------------------------------------------------------------------------------------------------ 43 | sealed void Invoke(T1 t1, T2 t2) 44 | { 45 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, t1, t2, m_pContext); 46 | s_CallbackReferences.RemoveItem(this); 47 | } 48 | }; 49 | 50 | class EDF_ScriptInvokerCallback3 : EDF_ScriptInvokerCallbackBase 51 | { 52 | //------------------------------------------------------------------------------------------------ 53 | sealed void Invoke(T1 t1, T2 t2, T3 t3) 54 | { 55 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, t1, t2, t3, m_pContext); 56 | s_CallbackReferences.RemoveItem(this); 57 | } 58 | }; 59 | 60 | class EDF_ScriptInvokerCallback4 : EDF_ScriptInvokerCallbackBase 61 | { 62 | //------------------------------------------------------------------------------------------------ 63 | sealed void Invoke(T1 t1, T2 t2, T3 t3, T4 t4) 64 | { 65 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, t1, t2, t3, t4, m_pContext); 66 | s_CallbackReferences.RemoveItem(this); 67 | } 68 | }; 69 | 70 | class EDF_ScriptInvokerCallback5 : EDF_ScriptInvokerCallbackBase 71 | { 72 | //------------------------------------------------------------------------------------------------ 73 | sealed void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) 74 | { 75 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, t1, t2, t3, t4, t5, m_pContext); 76 | s_CallbackReferences.RemoveItem(this); 77 | } 78 | }; 79 | 80 | class EDF_ScriptInvokerCallback6 : EDF_ScriptInvokerCallbackBase 81 | { 82 | //------------------------------------------------------------------------------------------------ 83 | sealed void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) 84 | { 85 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, t1, t2, t3, t4, t5, t6, m_pContext); 86 | s_CallbackReferences.RemoveItem(this); 87 | } 88 | }; 89 | 90 | class EDF_ScriptInvokerCallback7 : EDF_ScriptInvokerCallbackBase 91 | { 92 | //------------------------------------------------------------------------------------------------ 93 | sealed void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) 94 | { 95 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, t1, t2, t3, t4, t5, t6, t7, m_pContext); 96 | s_CallbackReferences.RemoveItem(this); 97 | } 98 | }; 99 | 100 | class EDF_ScriptInvokerCallback8 : EDF_ScriptInvokerCallbackBase 101 | { 102 | //------------------------------------------------------------------------------------------------ 103 | sealed void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) 104 | { 105 | GetGame().GetScriptModule().Call(m_pInvokeInstance, m_sInvokeMethod, false, null, t1, t2, t3, t4, t5, t6, t7, t8, m_pContext); 106 | s_CallbackReferences.RemoveItem(this); 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /src/Scripts/Game/SCR_BaseGameMode.c: -------------------------------------------------------------------------------- 1 | modded class SCR_BaseGameMode 2 | { 3 | //------------------------------------------------------------------------------------------------ 4 | override void OnGameEnd() 5 | { 6 | // On game end before any final flushes, hard map the async api into sync to make those operations blocking and not loose data 7 | EDF_DbDriver.s_bForceBlocking = true; 8 | super.OnGameEnd(); 9 | } 10 | 11 | //------------------------------------------------------------------------------------------------ 12 | void ~SCR_BaseGameMode() 13 | { 14 | // TODO: Find better event for "session" teardown that works on dedicated servers, workbench and player hosted missions. 15 | EDF_DbDriver.s_bForceBlocking = false; 16 | EDF_DbEntityIdGenerator.Reset(); 17 | EDF_WebProxyDbDriverCallback.Reset(); 18 | EDF_DbRepositoryFactory.ResetCache(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Scripts/WorkbenchGame/WorldEditor/EDF_ClearDatabasePlugin.c: -------------------------------------------------------------------------------- 1 | [WorkbenchPluginAttribute("Clear Database", "Deletes the .db folder!", "alt+c", "", {"WorldEditor", "ResourceManager"}, "", 0xf1c0)] 2 | class EDF_ClearDatabasePlugin: WorkbenchPlugin 3 | { 4 | const string DB_BASE_DIR = "$profile:/.db"; 5 | 6 | //------------------------------------------------------------------------------------------------ 7 | override void Run() 8 | { 9 | FileIO.FindFiles(DeleteFileCallback, DB_BASE_DIR, ".json"); 10 | FileIO.FindFiles(DeleteFileCallback, DB_BASE_DIR, ".bin"); 11 | FileIO.FindFiles(DeleteFileCallback, DB_BASE_DIR, string.Empty); 12 | FileIO.DeleteFile(DB_BASE_DIR); 13 | Print("-- DATABASE CLEARED --", LogLevel.WARNING); 14 | } 15 | 16 | //------------------------------------------------------------------------------------------------ 17 | protected void DeleteFileCallback(string path, FileAttribute attributes) 18 | { 19 | FileIO.DeleteFile(path); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arkensor/EnfusionDatabaseFramework/20c0fbf251fae89446a61fa511649850c29c0b52/src/thumbnail.png -------------------------------------------------------------------------------- /tests/EnfusionDatabaseFramework.Tests.gproj: -------------------------------------------------------------------------------- 1 | GameProject { 2 | ID "EnfusionDatabaseFramework.Tests" 3 | GUID "5D6EA89D94B07EDF" 4 | TITLE "Enfusion Database Framework Tests" 5 | Dependencies { 6 | "5D6EA74A94173EDF" "58D0FB3206B6F859" 7 | } 8 | } -------------------------------------------------------------------------------- /tests/Scripts/Game/Drivers/EDF_DbDriverBufferWrapperTests.c: -------------------------------------------------------------------------------- 1 | /* 2 | class EDF_DbDriverBufferWrapperTests : TestSuite 3 | { 4 | //------------------------------------------------------------------------------------------------ 5 | [Step(EStage.Setup)] 6 | void Setup() 7 | { 8 | } 9 | 10 | //------------------------------------------------------------------------------------------------ 11 | [Step(EStage.TearDown)] 12 | void TearDown() 13 | { 14 | } 15 | }; 16 | 17 | class EDF_Test_DbDriverBufferWrapperEntity : EDF_DbEntity 18 | { 19 | int m_iValue; 20 | 21 | //------------------------------------------------------------------------------------------------ 22 | static EDF_Test_DbDriverBufferWrapperEntity Create(string id, int value) 23 | { 24 | EDF_Test_DbDriverBufferWrapperEntity instance(); 25 | instance.SetId(id); 26 | instance.m_iValue = value; 27 | return instance; 28 | } 29 | }; 30 | 31 | //------------------------------------------------------------------------------------------------ 32 | [Test("EDF_DbDriverBufferWrapperTests")] 33 | TestResultBase EDF_Test_DbDriverBufferWrapper_AddOrUpdateFindById_NotFlushed_Returned() 34 | { 35 | // Arrange 36 | EDF_InMemoryDbDriver driver(); 37 | EDF_InMemoryDbConnectionInfo connectInfo(); 38 | connectInfo.m_sDatabaseName = "Testing"; 39 | driver.initialize(connectInfo); 40 | EDF_DbDriverBufferWrapper bufferedDriver(driver); 41 | 42 | auto entity = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000001", 42); 43 | 44 | // Act 45 | EDF_EDbOperationStatusCode statusCode = bufferedDriver.AddOrUpdate(entity); 46 | 47 | // Assert 48 | if (statusCode != EDF_EDbOperationStatusCode.SUCCESS) 49 | return new EDF_TestResult(false); 50 | 51 | array results = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, EDF_DbFind.Id().Equals(entity.GetId())).GetEntities(); 52 | if (results.Count() != 1) 53 | return new EDF_TestResult(false); 54 | 55 | EDF_Test_DbDriverBufferWrapperEntity resultEntity = EDF_Test_DbDriverBufferWrapperEntity.Cast(results.Get(0)); 56 | return new EDF_TestResult( 57 | resultEntity.GetId() == entity.GetId() && 58 | resultEntity.m_iValue == entity.m_iValue); 59 | }; 60 | 61 | //------------------------------------------------------------------------------------------------ 62 | [Test("EDF_DbDriverBufferWrapperTests")] 63 | TestResultBase EDF_Test_DbDriverBufferWrapper_AddOrUpdateInPlaceTwice_Flushed_Returned() 64 | { 65 | // Arrange 66 | EDF_InMemoryDbDriver driver(); 67 | EDF_InMemoryDbConnectionInfo connectInfo(); 68 | connectInfo.m_sDatabaseName = "Testing"; 69 | driver.initialize(connectInfo); 70 | EDF_DbDriverBufferWrapper bufferedDriver(driver); 71 | 72 | auto entity = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000001", 42); 73 | auto updatedEntity = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000001", 1337); 74 | 75 | // Act 76 | EDF_EDbOperationStatusCode statusCode1 = bufferedDriver.AddOrUpdate(entity); 77 | EDF_EDbOperationStatusCode statusCode2 = bufferedDriver.AddOrUpdate(updatedEntity); 78 | bufferedDriver.Flush(forceBlocking: true); 79 | 80 | // Assert 81 | if (statusCode1 != EDF_EDbOperationStatusCode.SUCCESS|| 82 | statusCode2 != EDF_EDbOperationStatusCode.SUCCESS) 83 | { 84 | return new EDF_TestResult(false); 85 | } 86 | 87 | array results = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, EDF_DbFind.Id().Equals(updatedEntity.GetId())).GetEntities(); 88 | if (results.Count() != 1) 89 | return new EDF_TestResult(false); 90 | 91 | EDF_Test_DbDriverBufferWrapperEntity resultEntity = EDF_Test_DbDriverBufferWrapperEntity.Cast(results.Get(0)); 92 | return new EDF_TestResult( 93 | resultEntity.GetId() == updatedEntity.GetId() && 94 | resultEntity.m_iValue == updatedEntity.m_iValue); 95 | }; 96 | 97 | //------------------------------------------------------------------------------------------------ 98 | [Test("EDF_DbDriverBufferWrapperTests")] 99 | TestResultBase EDF_Test_DbDriverBufferWrapper_AddOrUpdateFindAllLimited_NotFlushed_ReturnedOnlyInLimit() 100 | { 101 | // Arrange 102 | EDF_InMemoryDbDriver driver(); 103 | EDF_InMemoryDbConnectionInfo connectInfo(); 104 | connectInfo.m_sDatabaseName = "Testing"; 105 | driver.initialize(connectInfo); 106 | EDF_DbDriverBufferWrapper bufferedDriver(driver); 107 | 108 | auto entity1 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000001", 42); 109 | auto entity2 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000002", 43); 110 | auto entity3 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000003", 44); 111 | 112 | // Act 113 | EDF_EDbOperationStatusCode statusCode1 = bufferedDriver.AddOrUpdate(entity1); 114 | EDF_EDbOperationStatusCode statusCode2 = bufferedDriver.AddOrUpdate(entity2); 115 | EDF_EDbOperationStatusCode statusCode3 = bufferedDriver.AddOrUpdate(entity3); 116 | 117 | // Assert 118 | if (statusCode1 != EDF_EDbOperationStatusCode.SUCCESS|| 119 | statusCode2 != EDF_EDbOperationStatusCode.SUCCESS|| 120 | statusCode3 != EDF_EDbOperationStatusCode.SUCCESS) 121 | { 122 | return new EDF_TestResult(false); 123 | } 124 | 125 | array results = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, limit: 2).GetEntities(); 126 | if (results.Count() != 2) 127 | return new EDF_TestResult(false); 128 | 129 | EDF_Test_DbDriverBufferWrapperEntity resultEntity1 = EDF_Test_DbDriverBufferWrapperEntity.Cast(results.Get(0)); 130 | EDF_Test_DbDriverBufferWrapperEntity resultEntity2 = EDF_Test_DbDriverBufferWrapperEntity.Cast(results.Get(1)); 131 | return new EDF_TestResult( 132 | resultEntity1.GetId() == entity1.GetId() && 133 | resultEntity2.GetId() == entity2.GetId()); 134 | }; 135 | 136 | //------------------------------------------------------------------------------------------------ 137 | [Test("EDF_DbDriverBufferWrapperTests")] 138 | TestResultBase EDF_Test_DbDriverBufferWrapper_AddOrUpdateFindMultipleByIdDesc_NotFlushed_Returned() 139 | { 140 | // Arrange 141 | EDF_InMemoryDbDriver driver(); 142 | EDF_InMemoryDbConnectionInfo connectInfo(); 143 | connectInfo.m_sDatabaseName = "Testing"; 144 | driver.initialize(connectInfo); 145 | EDF_DbDriverBufferWrapper bufferedDriver(driver); 146 | 147 | auto entity1 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000001", 42); 148 | auto entity2 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000002", 43); 149 | auto entity3 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000003", 44); 150 | 151 | // Act 152 | EDF_EDbOperationStatusCode statusCode1 = bufferedDriver.AddOrUpdate(entity1); 153 | EDF_EDbOperationStatusCode statusCode2 = bufferedDriver.AddOrUpdate(entity2); 154 | EDF_EDbOperationStatusCode statusCode3 = bufferedDriver.AddOrUpdate(entity3); 155 | 156 | // Assert 157 | if (statusCode1 != EDF_EDbOperationStatusCode.SUCCESS|| 158 | statusCode2 != EDF_EDbOperationStatusCode.SUCCESS|| 159 | statusCode3 != EDF_EDbOperationStatusCode.SUCCESS) 160 | { 161 | return new EDF_TestResult(false); 162 | } 163 | 164 | array results = bufferedDriver.FindAll( 165 | EDF_Test_DbDriverBufferWrapperEntity, 166 | EDF_DbFind.Id().EqualsAnyOf(EDF_DbValues.From({"TEST0000-0000-0001-0000-000000000002", "TEST0000-0000-0001-0000-000000000003"})), 167 | orderBy: {{"m_iValue", EDF_EDbEntitySortDirection.DESCENDING}} 168 | ).GetEntities(); 169 | 170 | if (results.Count() != 2) 171 | return new EDF_TestResult(false); 172 | 173 | EDF_Test_DbDriverBufferWrapperEntity resultEntity1 = EDF_Test_DbDriverBufferWrapperEntity.Cast(results.Get(0)); 174 | EDF_Test_DbDriverBufferWrapperEntity resultEntity2 = EDF_Test_DbDriverBufferWrapperEntity.Cast(results.Get(1)); 175 | return new EDF_TestResult( 176 | resultEntity1.GetId() == entity3.GetId() && 177 | resultEntity2.GetId() == entity2.GetId()); 178 | }; 179 | 180 | //------------------------------------------------------------------------------------------------ 181 | [Test("EDF_DbDriverBufferWrapperTests")] 182 | TestResultBase EDF_Test_DbDriverBufferWrapper_AddRemove_NotFlushed_NotRetured() 183 | { 184 | // Arrange 185 | EDF_InMemoryDbDriver driver(); 186 | EDF_InMemoryDbConnectionInfo connectInfo(); 187 | connectInfo.m_sDatabaseName = "Testing"; 188 | driver.initialize(connectInfo); 189 | EDF_DbDriverBufferWrapper bufferedDriver(driver); 190 | 191 | auto entity = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000001", 42); 192 | 193 | // Act 194 | EDF_EDbOperationStatusCode statusCode1 = bufferedDriver.AddOrUpdate(entity); 195 | EDF_EDbOperationStatusCode statusCode2 = bufferedDriver.Remove(EDF_Test_DbDriverBufferWrapperEntity, entity.GetId()); 196 | 197 | // Assert 198 | if (statusCode1 != EDF_EDbOperationStatusCode.SUCCESS || 199 | statusCode2 != EDF_EDbOperationStatusCode.SUCCESS) 200 | { 201 | return new EDF_TestResult(false); 202 | } 203 | 204 | array results = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, EDF_DbFind.Id().Equals(entity.GetId())).GetEntities(); 205 | return new EDF_TestResult(results.Count() == 0); 206 | }; 207 | 208 | //------------------------------------------------------------------------------------------------ 209 | [Test("EDF_DbDriverBufferWrapperTests")] 210 | TestResultBase EDF_Test_DbDriverBufferWrapper_AddRemove_Flushed_NotRetured() 211 | { 212 | // Arrange 213 | EDF_InMemoryDbDriver driver(); 214 | EDF_InMemoryDbConnectionInfo connectInfo(); 215 | connectInfo.m_sDatabaseName = "Testing"; 216 | driver.initialize(connectInfo); 217 | EDF_DbDriverBufferWrapper bufferedDriver(driver); 218 | 219 | auto entity = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000001", 42); 220 | 221 | // Act 222 | EDF_EDbOperationStatusCode statusCode1 = bufferedDriver.AddOrUpdate(entity); 223 | EDF_EDbOperationStatusCode statusCode2 = bufferedDriver.Remove(EDF_Test_DbDriverBufferWrapperEntity, entity.GetId()); 224 | bufferedDriver.Flush(forceBlocking: true); 225 | 226 | // Assert 227 | if (statusCode1 != EDF_EDbOperationStatusCode.SUCCESS || 228 | statusCode2 != EDF_EDbOperationStatusCode.SUCCESS) 229 | { 230 | return new EDF_TestResult(false); 231 | } 232 | 233 | array results = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, EDF_DbFind.Id().Equals(entity.GetId())).GetEntities(); 234 | return new EDF_TestResult(results.Count() == 0); 235 | }; 236 | 237 | //------------------------------------------------------------------------------------------------ 238 | [Test("EDF_DbDriverBufferWrapperTests")] 239 | TestResultBase EDF_Test_DbDriverBufferWrapper_AddFlushRemove_NotFlushed_NotRetured() 240 | { 241 | // Arrange 242 | EDF_InMemoryDbDriver driver(); 243 | EDF_InMemoryDbConnectionInfo connectInfo(); 244 | connectInfo.m_sDatabaseName = "Testing"; 245 | driver.initialize(connectInfo); 246 | EDF_DbDriverBufferWrapper bufferedDriver(driver); 247 | 248 | auto entity = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000001", 42); 249 | 250 | // Act 251 | EDF_EDbOperationStatusCode statusCode1 = bufferedDriver.AddOrUpdate(entity); 252 | bufferedDriver.Flush(forceBlocking: true); 253 | EDF_EDbOperationStatusCode statusCode2 = bufferedDriver.Remove(EDF_Test_DbDriverBufferWrapperEntity, entity.GetId()); 254 | 255 | // Assert 256 | if (statusCode1 != EDF_EDbOperationStatusCode.SUCCESS || 257 | statusCode2 != EDF_EDbOperationStatusCode.SUCCESS) 258 | { 259 | return new EDF_TestResult(false); 260 | } 261 | 262 | array bufferedResults = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, EDF_DbFind.Id().Equals(entity.GetId())).GetEntities(); 263 | array dbResults = driver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, EDF_DbFind.Id().Equals(entity.GetId())).GetEntities(); 264 | return new EDF_TestResult(bufferedResults.Count() == 0 && dbResults.Count() == 1); 265 | }; 266 | 267 | //------------------------------------------------------------------------------------------------ 268 | [Test("EDF_DbDriverBufferWrapperTests")] 269 | TestResultBase EDF_Test_DbDriverBufferWrapper_FindAllPaginatedOrdered_HalfFlushed_CorrectOrder() 270 | { 271 | // Arrange 272 | EDF_InMemoryDbDriver driver(); 273 | EDF_InMemoryDbConnectionInfo connectInfo(); 274 | connectInfo.m_sDatabaseName = "Testing"; 275 | driver.initialize(connectInfo); 276 | EDF_DbDriverBufferWrapper bufferedDriver(driver); 277 | 278 | auto entity1 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000001", 11); 279 | auto entity2 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000002", 22); 280 | auto entity3 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000003", 33); // Deleted, Not flushed 281 | auto entity4 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000004", 44); // Deleted, Not flushed 282 | auto entity5 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000005", 55); 283 | auto entity6 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000006", 66); 284 | auto entity7 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000007", 77); 285 | auto entity8 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000008", 88); //Not flushed 286 | auto entity9 = EDF_Test_DbDriverBufferWrapperEntity.Create("TEST0000-0000-0001-0000-000000000009", 99); //Not flushed 287 | 288 | // Act 289 | bufferedDriver.AddOrUpdate(entity1); 290 | bufferedDriver.AddOrUpdate(entity2); 291 | //bufferedDriver.AddOrUpdate(entity3); 292 | //bufferedDriver.AddOrUpdate(entity4); 293 | bufferedDriver.AddOrUpdate(entity5); 294 | bufferedDriver.AddOrUpdate(entity6); 295 | bufferedDriver.AddOrUpdate(entity7); 296 | bufferedDriver.Flush(forceBlocking: true); 297 | //bufferedDriver.Remove(EDF_Test_DbDriverBufferWrapperEntity, entity3.GetId()); 298 | //bufferedDriver.Remove(EDF_Test_DbDriverBufferWrapperEntity, entity4.GetId()); 299 | bufferedDriver.AddOrUpdate(entity3); 300 | bufferedDriver.AddOrUpdate(entity4); 301 | bufferedDriver.AddOrUpdate(entity8); 302 | bufferedDriver.AddOrUpdate(entity9); 303 | 304 | // Assert 305 | 306 | EDF_DbFindCondition condition = null; //EDF_DbFind.Id().Equals(entity.GetId()) 307 | array orderBy = {{"m_iValue", EDF_EDbEntitySortDirection.ASCENDING}}; //null; 308 | 309 | array page1 = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, condition, orderBy, offset: 0, limit: 3).GetEntities(); 310 | array page2 = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, condition, orderBy, offset: 3, limit: 3).GetEntities(); 311 | array page3 = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, condition, orderBy, offset: 6, limit: 3).GetEntities(); 312 | 313 | //array page1 = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, condition, orderBy, offset: 0, limit: 2).GetEntities(); 314 | //array page2 = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, condition, orderBy, offset: 2, limit: 2).GetEntities(); 315 | //array page3 = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, condition, orderBy, offset: 4, limit: 2).GetEntities(); 316 | //array page4 = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, condition, orderBy, offset: 6, limit: 2).GetEntities(); 317 | //array page5 = bufferedDriver.FindAll(EDF_Test_DbDriverBufferWrapperEntity, condition, orderBy, offset: 8, limit: 2).GetEntities(); 318 | 319 | return new EDF_TestResult(true); 320 | }; 321 | */ 322 | -------------------------------------------------------------------------------- /tests/Scripts/Game/Drivers/InMemory/EDF_InMemoryDbDriverTests.c: -------------------------------------------------------------------------------- 1 | class EDF_InMemoryDbDriverTests : TestSuite 2 | { 3 | //------------------------------------------------------------------------------------------------ 4 | [Step(EStage.Setup)] 5 | void Setup() 6 | { 7 | } 8 | 9 | //------------------------------------------------------------------------------------------------ 10 | [Step(EStage.TearDown)] 11 | void TearDown() 12 | { 13 | } 14 | } 15 | 16 | class EDF_Test_InMemoryDbDriverEntity : EDF_DbEntity 17 | { 18 | float m_fFloatValue; 19 | string m_sStringValue; 20 | 21 | //------------------------------------------------------------------------------------------------ 22 | void EDF_Test_InMemoryDbDriverEntity(string id, float floatValue, string stringValue) 23 | { 24 | SetId(id); 25 | m_fFloatValue = floatValue; 26 | m_sStringValue = stringValue; 27 | } 28 | } 29 | 30 | //------------------------------------------------------------------------------------------------ 31 | [Test("EDF_InMemoryDbDriverTests")] 32 | TestResultBase EDF_Test_InMemoryDbDriver_AddOrUpdate_NewEntity_Inserted() 33 | { 34 | // Arrange 35 | EDF_InMemoryDbDriver driver(); 36 | EDF_InMemoryDbConnectionInfo connectInfo(); 37 | connectInfo.m_sDatabaseName = "Testing"; 38 | driver.Initialize(connectInfo); 39 | 40 | EDF_Test_InMemoryDbDriverEntity entity("TEST0000-0000-0001-0000-000000000001", 42.42, "Hello World"); 41 | 42 | // Act 43 | EDF_EDbOperationStatusCode statusCode = driver.AddOrUpdate(entity); 44 | 45 | // Assert 46 | if (!statusCode == EDF_EDbOperationStatusCode.SUCCESS) return new EDF_TestResult(false); 47 | 48 | array results = driver.FindAll(EDF_Test_InMemoryDbDriverEntity, EDF_DbFind.Id().Equals(entity.GetId())).GetEntities(); 49 | 50 | if (results.Count() != 1) return new EDF_TestResult(false); 51 | 52 | EDF_Test_InMemoryDbDriverEntity resultEntity = EDF_Test_InMemoryDbDriverEntity.Cast(results.Get(0)); 53 | 54 | if (!resultEntity) return new EDF_TestResult(false); 55 | 56 | return new EDF_TestResult( 57 | resultEntity.GetId() == entity.GetId() && 58 | resultEntity.m_fFloatValue == entity.m_fFloatValue && 59 | resultEntity.m_sStringValue == entity.m_sStringValue); 60 | } 61 | 62 | //------------------------------------------------------------------------------------------------ 63 | [Test("EDF_InMemoryDbDriverTests")] 64 | TestResultBase EDF_Test_InMemoryDbDriver_Remove_ExistingId_Removed() 65 | { 66 | // Arrange 67 | EDF_InMemoryDbDriver driver(); 68 | EDF_InMemoryDbConnectionInfo connectInfo(); 69 | connectInfo.m_sDatabaseName = "Testing"; 70 | driver.Initialize(connectInfo); 71 | 72 | EDF_Test_InMemoryDbDriverEntity entity("TEST0000-0000-0001-0000-000000000002", 42.42, "Hello World"); 73 | driver.AddOrUpdate(entity); 74 | 75 | // Act 76 | EDF_EDbOperationStatusCode statusCode = driver.Remove(EDF_Test_InMemoryDbDriverEntity, entity.GetId()); 77 | 78 | // Assert 79 | if (!statusCode == EDF_EDbOperationStatusCode.SUCCESS) return new EDF_TestResult(false); 80 | 81 | array results = driver.FindAll(EDF_Test_InMemoryDbDriverEntity, EDF_DbFind.Id().Equals(entity.GetId())).GetEntities(); 82 | 83 | return new EDF_TestResult(results.Count() == 0); 84 | } 85 | 86 | -------------------------------------------------------------------------------- /tests/Scripts/Game/Drivers/LocalFile/EDF_BinaryFileDbDriverTests.c: -------------------------------------------------------------------------------- 1 | class EDF_BinaryFileDbDriverTests : TestSuite 2 | { 3 | static const string DB_NAME = "BinaryFileDbDriverTests"; 4 | 5 | //------------------------------------------------------------------------------------------------ 6 | [Step(EStage.Setup)] 7 | void Setup() 8 | { 9 | } 10 | 11 | //------------------------------------------------------------------------------------------------ 12 | [Step(EStage.TearDown)] 13 | void TearDown() 14 | { 15 | string dir = string.Format("%1/%2", EDF_FileDbDriverBase.DB_BASE_DIR, DB_NAME); 16 | 17 | array paths = {}; 18 | FileIO.FindFiles(paths.Insert, dir, ""); 19 | 20 | foreach (string path : paths) 21 | { 22 | FileIO.DeleteFile(path); 23 | } 24 | 25 | FileIO.DeleteFile(dir); 26 | } 27 | 28 | //------------------------------------------------------------------------------------------------ 29 | static void WriteEntity(string dbDir, EDF_Test_BinFileDbDriverEntity entity) 30 | { 31 | SCR_BinSaveContext writer(); 32 | writer.WriteValue("data", entity); 33 | FileIO.MakeDirectory(dbDir); 34 | writer.SaveToFile(string.Format("%1/%2.bin", dbDir, entity.GetId())); 35 | } 36 | 37 | //------------------------------------------------------------------------------------------------ 38 | static void DeleteEntity(string dbDir, string entityId) 39 | { 40 | FileIO.DeleteFile(string.Format("%1/%2.bin", dbDir, entityId)); 41 | } 42 | }; 43 | 44 | class EDF_Test_BinFileDbDriverEntity : EDF_DbEntity 45 | { 46 | float m_fFloatValue; 47 | string m_sStringValue; 48 | 49 | //------------------------------------------------------------------------------------------------ 50 | static EDF_Test_BinFileDbDriverEntity Create(string id, float floatValue, string stringValue) 51 | { 52 | EDF_Test_BinFileDbDriverEntity entity(); 53 | entity.SetId(id); 54 | entity.m_fFloatValue = floatValue; 55 | entity.m_sStringValue = stringValue; 56 | return entity; 57 | } 58 | }; 59 | 60 | class EDF_Test_BinFileDbDriverEntity_TestBase : TestBase 61 | { 62 | ref EDF_BinaryFileDbDriver driver; 63 | 64 | //------------------------------------------------------------------------------------------------ 65 | [Step(EStage.Setup)] 66 | void Construct() 67 | { 68 | driver = new EDF_BinaryFileDbDriver(); 69 | } 70 | }; 71 | 72 | [Test("EDF_BinaryFileDbDriverTests")] 73 | class EDF_Test_BinaryFileDbDriver_AddOrUpdate_NewEntity_ReadFromFileSuccessfully : EDF_Test_BinFileDbDriverEntity_TestBase 74 | { 75 | //------------------------------------------------------------------------------------------------ 76 | [Step(EStage.Setup)] 77 | void Arrange() 78 | { 79 | EDF_BinaryFileDbConnectionInfo connectInfo(); 80 | connectInfo.m_sDatabaseName = EDF_BinaryFileDbDriverTests.DB_NAME; 81 | driver.Initialize(connectInfo); 82 | } 83 | 84 | //------------------------------------------------------------------------------------------------ 85 | [Step(EStage.Main)] 86 | void ActAndAsset() 87 | { 88 | auto entity = EDF_Test_BinFileDbDriverEntity.Create("TEST0000-0000-0001-0000-000000000001", 42.42, "Hello World"); 89 | EDF_EDbOperationStatusCode statusCode = driver.AddOrUpdate(entity); 90 | 91 | // Assert 92 | if (statusCode != EDF_EDbOperationStatusCode.SUCCESS) 93 | { 94 | SetResult(new EDF_TestResult(false)); 95 | return; 96 | } 97 | 98 | array results = driver.FindAll(EDF_Test_BinFileDbDriverEntity, EDF_DbFind.Id().Equals(entity.GetId())).GetEntities(); 99 | 100 | if (results.Count() != 1) 101 | { 102 | SetResult(new EDF_TestResult(false)); 103 | return; 104 | } 105 | 106 | EDF_Test_BinFileDbDriverEntity resultEntity = EDF_Test_BinFileDbDriverEntity.Cast(results.Get(0)); 107 | 108 | if (!resultEntity) 109 | { 110 | SetResult(new EDF_TestResult(false)); 111 | return; 112 | } 113 | 114 | SetResult(new EDF_TestResult( 115 | resultEntity.GetId() == entity.GetId() && 116 | resultEntity.m_fFloatValue == entity.m_fFloatValue && 117 | resultEntity.m_sStringValue == entity.m_sStringValue)); 118 | } 119 | 120 | //------------------------------------------------------------------------------------------------ 121 | [Step(EStage.TearDown)] 122 | void Cleanup() 123 | { 124 | EDF_BinaryFileDbDriverTests.DeleteEntity(driver._GetTypeDirectory(EDF_Test_BinFileDbDriverEntity), "TEST0000-0000-0001-0000-000000000001"); 125 | } 126 | }; 127 | -------------------------------------------------------------------------------- /tests/Scripts/Game/Drivers/LocalFile/EDF_JsonFileDbDriverTests.c: -------------------------------------------------------------------------------- 1 | class EDF_JsonFileDbDriverTests : TestSuite 2 | { 3 | static const string DB_NAME = "JsonFileDbDriverTests"; 4 | 5 | //------------------------------------------------------------------------------------------------ 6 | [Step(EStage.Setup)] 7 | void Setup() 8 | { 9 | } 10 | 11 | //------------------------------------------------------------------------------------------------ 12 | [Step(EStage.TearDown)] 13 | void TearDown() 14 | { 15 | string dir = string.Format("%1/%2", EDF_FileDbDriverBase.DB_BASE_DIR, DB_NAME); 16 | 17 | array paths = {}; 18 | FileIO.FindFiles(paths.Insert, dir, ""); 19 | 20 | foreach (string path : paths) 21 | { 22 | FileIO.DeleteFile(path); 23 | } 24 | 25 | FileIO.DeleteFile(dir); 26 | } 27 | 28 | //------------------------------------------------------------------------------------------------ 29 | static void WriteEntity(string dbDir, EDF_Test_JsonFileDbDriverEntity entity) 30 | { 31 | SCR_JsonSaveContext writer(); 32 | writer.WriteValue("", entity); 33 | FileIO.MakeDirectory(dbDir); 34 | writer.SaveToFile(string.Format("%1/%2.json", dbDir, entity.GetId())); 35 | } 36 | 37 | //------------------------------------------------------------------------------------------------ 38 | static void DeleteEntity(string dbDir, string entityId) 39 | { 40 | FileIO.DeleteFile(string.Format("%1/%2.json", dbDir, entityId)); 41 | } 42 | } 43 | 44 | class EDF_Test_JsonFileDbDriverEntity : EDF_DbEntity 45 | { 46 | float m_fFloatValue; 47 | string m_sStringValue; 48 | 49 | //------------------------------------------------------------------------------------------------ 50 | void EDF_Test_JsonFileDbDriverEntity(string id, float floatValue, string stringValue) 51 | { 52 | SetId(id); 53 | m_fFloatValue = floatValue; 54 | m_sStringValue = stringValue; 55 | } 56 | } 57 | 58 | class EDF_Test_JsonFileDbDriver_TestBase : TestBase 59 | { 60 | ref EDF_JsonFileDbDriver driver; 61 | 62 | //------------------------------------------------------------------------------------------------ 63 | [Step(EStage.Setup)] 64 | void Construct() 65 | { 66 | driver = new EDF_JsonFileDbDriver(); 67 | } 68 | } 69 | 70 | [Test("EDF_JsonFileDbDriverTests")] 71 | class EDF_Test_JsonFileDbDriver_AddOrUpdate_NewEntity_ReadFromFileSuccessfully : EDF_Test_JsonFileDbDriver_TestBase 72 | { 73 | //------------------------------------------------------------------------------------------------ 74 | [Step(EStage.Setup)] 75 | void Arrange() 76 | { 77 | EDF_JsonFileDbConnectionInfo connectInfo(); 78 | connectInfo.m_sDatabaseName = EDF_JsonFileDbDriverTests.DB_NAME; 79 | driver.Initialize(connectInfo); 80 | } 81 | 82 | //------------------------------------------------------------------------------------------------ 83 | [Step(EStage.Main)] 84 | void ActAndAsset() 85 | { 86 | EDF_Test_JsonFileDbDriverEntity entity("TEST0000-0000-0001-0000-000000000001", 42.42, "Hello World"); 87 | EDF_EDbOperationStatusCode statusCode = driver.AddOrUpdate(entity); 88 | 89 | // Assert 90 | if (statusCode != EDF_EDbOperationStatusCode.SUCCESS) 91 | { 92 | SetResult(new EDF_TestResult(false)); 93 | return; 94 | } 95 | 96 | array results = driver.FindAll(EDF_Test_JsonFileDbDriverEntity, EDF_DbFind.Id().Equals(entity.GetId())).GetEntities(); 97 | 98 | if (results.Count() != 1) 99 | { 100 | SetResult(new EDF_TestResult(false)); 101 | return; 102 | } 103 | 104 | EDF_Test_JsonFileDbDriverEntity resultEntity = EDF_Test_JsonFileDbDriverEntity.Cast(results.Get(0)); 105 | 106 | if (!resultEntity) 107 | { 108 | SetResult(new EDF_TestResult(false)); 109 | return; 110 | } 111 | 112 | SetResult(new EDF_TestResult( 113 | resultEntity.GetId() == entity.GetId() && 114 | float.AlmostEqual(resultEntity.m_fFloatValue, entity.m_fFloatValue) && 115 | resultEntity.m_sStringValue == entity.m_sStringValue)); 116 | } 117 | 118 | //------------------------------------------------------------------------------------------------ 119 | [Step(EStage.TearDown)] 120 | void Cleanup() 121 | { 122 | EDF_JsonFileDbDriverTests.DeleteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), "TEST0000-0000-0001-0000-000000000001"); 123 | } 124 | } 125 | 126 | [Test("EDF_JsonFileDbDriverTests")] 127 | class EDF_Test_JsonFileDbDriver_Remove_ExistingEntity_FileDeleted : EDF_Test_JsonFileDbDriver_TestBase 128 | { 129 | //------------------------------------------------------------------------------------------------ 130 | [Step(EStage.Setup)] 131 | void Arrange() 132 | { 133 | EDF_JsonFileDbConnectionInfo connectInfo(); 134 | connectInfo.m_sDatabaseName = EDF_JsonFileDbDriverTests.DB_NAME; 135 | driver.Initialize(connectInfo); 136 | } 137 | 138 | //------------------------------------------------------------------------------------------------ 139 | [Step(EStage.Main)] 140 | void ActAndAsset() 141 | { 142 | EDF_Test_JsonFileDbDriverEntity entity("TEST0000-0000-0001-0000-000000000002", 42.42, "Hello World"); 143 | driver.AddOrUpdate(entity); 144 | 145 | EDF_EDbOperationStatusCode statusCode = driver.Remove(EDF_Test_JsonFileDbDriverEntity, entity.GetId()); 146 | 147 | // Assert 148 | if (statusCode != EDF_EDbOperationStatusCode.SUCCESS) 149 | { 150 | SetResult(new EDF_TestResult(false)); 151 | return; 152 | } 153 | 154 | string file = string.Format("%1/%2.json", driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), entity.GetId()); 155 | SetResult(new EDF_TestResult(!FileIO.FileExists(file))); 156 | } 157 | 158 | //------------------------------------------------------------------------------------------------ 159 | [Step(EStage.TearDown)] 160 | void Cleanup() 161 | { 162 | EDF_JsonFileDbDriverTests.DeleteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), "TEST0000-0000-0001-0000-000000000002"); 163 | } 164 | } 165 | 166 | [Test("EDF_JsonFileDbDriverTests")] 167 | class EDF_Test_JsonFileDbDriver_FindAll_IdOnly_ExactLoadAndCache : EDF_Test_JsonFileDbDriver_TestBase 168 | { 169 | //------------------------------------------------------------------------------------------------ 170 | [Step(EStage.Setup)] 171 | void Arrange() 172 | { 173 | EDF_JsonFileDbConnectionInfo connectInfo(); 174 | connectInfo.m_sDatabaseName = EDF_JsonFileDbDriverTests.DB_NAME; 175 | connectInfo.m_bUseCache = true; 176 | driver.Initialize(connectInfo); 177 | 178 | EDF_JsonFileDbDriverTests.WriteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), new EDF_Test_JsonFileDbDriverEntity("TEST0000-0000-0001-0000-000000001001", 41.1, "Existing 1001")); 179 | EDF_JsonFileDbDriverTests.WriteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), new EDF_Test_JsonFileDbDriverEntity("TEST0000-0000-0001-0000-000000001002", 41.2, "Existing 1002")); 180 | EDF_JsonFileDbDriverTests.WriteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), new EDF_Test_JsonFileDbDriverEntity("TEST0000-0000-0001-0000-000000001003", 41.3, "Existing 1003")); 181 | EDF_JsonFileDbDriverTests.WriteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), new EDF_Test_JsonFileDbDriverEntity("TEST0000-0000-0001-0000-000000001004", 41.4, "Existing 1004")); 182 | EDF_JsonFileDbDriverTests.WriteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), new EDF_Test_JsonFileDbDriverEntity("TEST0000-0000-0001-0000-000000001005", 41.5, "Existing 1005")); 183 | } 184 | 185 | //------------------------------------------------------------------------------------------------ 186 | [Step(EStage.Main)] 187 | void ActAndAsset() 188 | { 189 | EDF_DbFindCondition condition = EDF_DbFind.Id().EqualsAnyOf({"TEST0000-0000-0001-0000-000000001001", "TEST0000-0000-0001-0000-000000001005"}); 190 | 191 | // Act 192 | array results = driver.FindAll(EDF_Test_JsonFileDbDriverEntity, condition).GetEntities(); 193 | 194 | // Assert 195 | SetResult(new EDF_TestResult((results.Count() == 2) && (driver._GetEntityCache().m_EntityInstances.Count() == 2))); 196 | } 197 | 198 | //------------------------------------------------------------------------------------------------ 199 | [Step(EStage.TearDown)] 200 | void Cleanup() 201 | { 202 | EDF_JsonFileDbDriverTests.DeleteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), "TEST0000-0000-0001-0000-000000001001"); 203 | EDF_JsonFileDbDriverTests.DeleteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), "TEST0000-0000-0001-0000-000000001002"); 204 | EDF_JsonFileDbDriverTests.DeleteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), "TEST0000-0000-0001-0000-000000001003"); 205 | EDF_JsonFileDbDriverTests.DeleteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), "TEST0000-0000-0001-0000-000000001004"); 206 | EDF_JsonFileDbDriverTests.DeleteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), "TEST0000-0000-0001-0000-000000001005"); 207 | } 208 | } 209 | 210 | [Test("EDF_JsonFileDbDriverTests")] 211 | class EDF_Test_JsonFileDbDriver_FindAll_ContentField_AllLoadedAndCached : EDF_Test_JsonFileDbDriver_TestBase 212 | { 213 | //------------------------------------------------------------------------------------------------ 214 | [Step(EStage.Setup)] 215 | void Arrange() 216 | { 217 | EDF_JsonFileDbConnectionInfo connectInfo(); 218 | connectInfo.m_sDatabaseName = EDF_JsonFileDbDriverTests.DB_NAME; 219 | connectInfo.m_bUseCache = true; 220 | driver.Initialize(connectInfo); 221 | 222 | EDF_JsonFileDbDriverTests.WriteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), new EDF_Test_JsonFileDbDriverEntity("TEST0000-0000-0001-0000-000000002001", 42.1, "Existing 2001")); 223 | EDF_JsonFileDbDriverTests.WriteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), new EDF_Test_JsonFileDbDriverEntity("TEST0000-0000-0001-0000-000000002002", 42.2, "Existing 2002")); 224 | EDF_JsonFileDbDriverTests.WriteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), new EDF_Test_JsonFileDbDriverEntity("TEST0000-0000-0001-0000-000000002003", 42.3, "Existing 2003")); 225 | EDF_JsonFileDbDriverTests.WriteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), new EDF_Test_JsonFileDbDriverEntity("TEST0000-0000-0001-0000-000000002004", 42.4, "Existing 2004")); 226 | EDF_JsonFileDbDriverTests.WriteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), new EDF_Test_JsonFileDbDriverEntity("TEST0000-0000-0001-0000-000000002005", 42.5, "Existing 2005")); 227 | } 228 | 229 | //------------------------------------------------------------------------------------------------ 230 | [Step(EStage.Main)] 231 | void ActAndAsset() 232 | { 233 | EDF_DbFindCondition condition = EDF_DbFind.Or({ 234 | EDF_DbFind.Field("m_sStringValue").Contains("2001"), 235 | EDF_DbFind.Field("m_fFloatValue").GreaterThanOrEquals(42.5) 236 | }); 237 | 238 | array results = driver.FindAll(EDF_Test_JsonFileDbDriverEntity, condition).GetEntities(); 239 | 240 | // Assert 241 | SetResult(new EDF_TestResult((results.Count() == 2) && (driver._GetEntityCache().m_EntityInstances.Count() == 5))); 242 | } 243 | 244 | //------------------------------------------------------------------------------------------------ 245 | [Step(EStage.TearDown)] 246 | void Cleanup() 247 | { 248 | EDF_JsonFileDbDriverTests.DeleteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), "TEST0000-0000-0001-0000-000000002001"); 249 | EDF_JsonFileDbDriverTests.DeleteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), "TEST0000-0000-0001-0000-000000002002"); 250 | EDF_JsonFileDbDriverTests.DeleteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), "TEST0000-0000-0001-0000-000000002003"); 251 | EDF_JsonFileDbDriverTests.DeleteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), "TEST0000-0000-0001-0000-000000002004"); 252 | EDF_JsonFileDbDriverTests.DeleteEntity(driver._GetTypeDirectory(EDF_Test_JsonFileDbDriverEntity), "TEST0000-0000-0001-0000-000000002005"); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /tests/Scripts/Game/EDF_DbEntityRepositoryTests.c: -------------------------------------------------------------------------------- 1 | class EDF_DbRepositoryTests : TestSuite 2 | { 3 | static ref EDF_DbContext m_pDbContext; 4 | 5 | //------------------------------------------------------------------------------------------------ 6 | [Step(EStage.Setup)] 7 | void Setup() 8 | { 9 | EDF_InMemoryDbConnectionInfo connectInfo(); 10 | connectInfo.m_sDatabaseName = "DbRepositoryTests"; 11 | m_pDbContext = EDF_DbContext.Create(connectInfo); 12 | } 13 | 14 | //------------------------------------------------------------------------------------------------ 15 | [Step(EStage.TearDown)] 16 | void TearDown() 17 | { 18 | m_pDbContext = null; 19 | } 20 | }; 21 | 22 | class EDF_Test_DbEntityRepositoryEntity : EDF_DbEntity 23 | { 24 | int m_iIntValue; 25 | 26 | //------------------------------------------------------------------------------------------------ 27 | void EDF_Test_DbEntityRepositoryEntity(string id, int intValue) 28 | { 29 | SetId(id); 30 | m_iIntValue = intValue; 31 | } 32 | }; 33 | 34 | [EDF_DbRepositoryRegistration()] 35 | class EDF_Test_DbEntityRepositoryEntityRepository : EDF_DbRepository 36 | { 37 | EDF_DbFindResultSingle FindByIntValue(int value) 38 | { 39 | return FindFirst(EDF_DbFind.Field("m_iIntValue").Equals(value)); 40 | } 41 | }; 42 | 43 | //------------------------------------------------------------------------------------------------ 44 | [Test("EDF_DbRepositoryTests")] 45 | TestResultBase EDF_Test_DbEntityRepository_AddOrUpdate_NewEntityFindByIntValue_Found() 46 | { 47 | // Arrange 48 | EDF_Test_DbEntityRepositoryEntityRepository repository = EDF_DbRepositoryHelper.Get(EDF_DbRepositoryTests.m_pDbContext); 49 | 50 | EDF_Test_DbEntityRepositoryEntity entity("TEST0000-0000-0001-0000-000000000001", 1001) 51 | 52 | // Act 53 | repository.AddOrUpdate(entity); 54 | 55 | // Assert 56 | EDF_TestResult result(repository.FindByIntValue(1001).GetEntity().GetId() == "TEST0000-0000-0001-0000-000000000001"); 57 | 58 | // Cleanup 59 | repository.Remove(entity); 60 | 61 | return result; 62 | }; 63 | 64 | //------------------------------------------------------------------------------------------------ 65 | [Test("EDF_DbRepositoryTests")] 66 | TestResultBase EDF_Test_DbEntityRepository_Remove_ByInstance_Removed() 67 | { 68 | // Arrange 69 | EDF_DbRepository repository = EDF_DbEntityHelper.GetRepository(EDF_DbRepositoryTests.m_pDbContext); 70 | 71 | EDF_Test_DbEntityRepositoryEntity entity("TEST0000-0000-0001-0000-000000000002", 1002); 72 | repository.AddOrUpdate(entity); 73 | 74 | // Act 75 | EDF_EDbOperationStatusCode statusCode = repository.Remove(entity); 76 | 77 | // Assert 78 | EDF_TestResult result( 79 | statusCode == EDF_EDbOperationStatusCode.SUCCESS && 80 | !repository.Find("TEST0000-0000-0001-0000-000000000002").GetEntity()); 81 | 82 | // Cleanup 83 | repository.Remove(entity); 84 | 85 | return result; 86 | }; 87 | -------------------------------------------------------------------------------- /tests/Scripts/Game/EDF_DbEntitySorterTests.c: -------------------------------------------------------------------------------- 1 | class EDF_DbEntitySorterTests : TestSuite 2 | { 3 | //------------------------------------------------------------------------------------------------ 4 | [Step(EStage.Setup)] 5 | void Setup() 6 | { 7 | } 8 | 9 | //------------------------------------------------------------------------------------------------ 10 | [Step(EStage.TearDown)] 11 | void TearDown() 12 | { 13 | } 14 | } 15 | 16 | class EDF_Test_DbEntitySortableEntity : EDF_DbEntity 17 | { 18 | int m_iIntValue; 19 | float m_fFloatValue; 20 | bool m_bBoolValue; 21 | string m_sStringValue; 22 | vector m_vVectorValue; 23 | 24 | //------------------------------------------------------------------------------------------------ 25 | void EDF_Test_DbEntitySortableEntity(int intVal = 0, float floatVal = 0.0, bool boolVal = false, string stringVal = "", vector vectorVal = "0 0 0") 26 | { 27 | m_iIntValue = intVal; 28 | m_fFloatValue = floatVal; 29 | m_bBoolValue = boolVal; 30 | m_sStringValue = stringVal; 31 | m_vVectorValue = vectorVal; 32 | } 33 | } 34 | 35 | class EDF_Test_DbEntitySortableEntitySingleWrapper : EDF_DbEntity 36 | { 37 | int m_iSameIntValue; 38 | ref EDF_Test_DbEntitySortableEntity m_pEntity; 39 | 40 | //------------------------------------------------------------------------------------------------ 41 | void EDF_Test_DbEntitySortableEntitySingleWrapper(int intVal = 0, float floatVal = 0.0, bool boolVal = false, string stringVal = "", vector vectorVal = "0 0 0") 42 | { 43 | m_iSameIntValue = 1337; 44 | m_pEntity = new EDF_Test_DbEntitySortableEntity(intVal, floatVal, boolVal, stringVal, vectorVal); 45 | } 46 | } 47 | 48 | //------------------------------------------------------------------------------------------------ 49 | [Test("EDF_DbEntitySorterTests")] 50 | TestResultBase EDF_Test_DbEntitySorter_GetSorted_ArrayInt_AscSorted() 51 | { 52 | // Arrange 53 | array entities = { 54 | new EDF_Test_DbEntitySortableEntity(intVal: 5), 55 | new EDF_Test_DbEntitySortableEntity(intVal: 50), 56 | new EDF_Test_DbEntitySortableEntity(intVal: 1) 57 | }; 58 | 59 | // Act 60 | array sorted = EDF_DbEntitySorter.GetSorted(entities, {{"m_iIntValue", EDF_EDbEntitySortDirection.ASCENDING}}); 61 | 62 | // Assert 63 | return new EDF_TestResult( 64 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(0)).m_iIntValue == 1 && 65 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(1)).m_iIntValue == 5 && 66 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(2)).m_iIntValue == 50); 67 | } 68 | 69 | //------------------------------------------------------------------------------------------------ 70 | [Test("EDF_DbEntitySorterTests")] 71 | TestResultBase EDF_Test_DbEntitySorter_GetSorted_ArrayInt_DescSorted() 72 | { 73 | // Arrange 74 | array entities = { 75 | new EDF_Test_DbEntitySortableEntity(intVal: 5), 76 | new EDF_Test_DbEntitySortableEntity(intVal: 50), 77 | new EDF_Test_DbEntitySortableEntity(intVal: 1) 78 | }; 79 | 80 | // Act 81 | array sorted = EDF_DbEntitySorter.GetSorted(entities, {{"m_iIntValue", EDF_EDbEntitySortDirection.DESCENDING}}); 82 | 83 | // Assert 84 | return new EDF_TestResult( 85 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(0)).m_iIntValue == 50 && 86 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(1)).m_iIntValue == 5 && 87 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(2)).m_iIntValue == 1); 88 | } 89 | 90 | //------------------------------------------------------------------------------------------------ 91 | [Test("EDF_DbEntitySorterTests")] 92 | TestResultBase EDF_Test_DbEntitySorter_GetSorted_ArrayFloat_AscSorted() 93 | { 94 | // Arrange 95 | array entities = { 96 | new EDF_Test_DbEntitySortableEntity(floatVal: 5.2), 97 | new EDF_Test_DbEntitySortableEntity(floatVal: 50.3), 98 | new EDF_Test_DbEntitySortableEntity(floatVal: 1.1) 99 | }; 100 | 101 | // Act 102 | array sorted = EDF_DbEntitySorter.GetSorted(entities, {{"m_fFloatValue", EDF_EDbEntitySortDirection.ASCENDING}}); 103 | 104 | // Assert 105 | return new EDF_TestResult( 106 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(0)).m_fFloatValue == 1.1 && 107 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(1)).m_fFloatValue == 5.2 && 108 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(2)).m_fFloatValue == 50.3); 109 | } 110 | 111 | //------------------------------------------------------------------------------------------------ 112 | [Test("EDF_DbEntitySorterTests")] 113 | TestResultBase EDF_Test_DbEntitySorter_GetSorted_ArrayFloat_DescSorted() 114 | { 115 | // Arrange 116 | array entities = { 117 | new EDF_Test_DbEntitySortableEntity(floatVal: 5.2), 118 | new EDF_Test_DbEntitySortableEntity(floatVal: 50.3), 119 | new EDF_Test_DbEntitySortableEntity(floatVal: 1.1) 120 | }; 121 | 122 | // Act 123 | array sorted = EDF_DbEntitySorter.GetSorted(entities, {{"m_fFloatValue", EDF_EDbEntitySortDirection.DESCENDING}}); 124 | 125 | // Assert 126 | return new EDF_TestResult( 127 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(0)).m_fFloatValue == 50.3 && 128 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(1)).m_fFloatValue == 5.2 && 129 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(2)).m_fFloatValue == 1.1); 130 | } 131 | 132 | //------------------------------------------------------------------------------------------------ 133 | [Test("EDF_DbEntitySorterTests")] 134 | TestResultBase EDF_Test_DbEntitySorter_GetSorted_WrappedIntFloatDirectionInvariant_DescSorted() 135 | { 136 | // Arrange 137 | array entities = { 138 | new EDF_Test_DbEntitySortableEntitySingleWrapper(floatVal: 5.2), 139 | new EDF_Test_DbEntitySortableEntitySingleWrapper(floatVal: 50.3), 140 | new EDF_Test_DbEntitySortableEntitySingleWrapper(floatVal: 1.1) 141 | }; 142 | 143 | // Act 144 | array sorted = EDF_DbEntitySorter.GetSorted(entities, {{"m_iSameIntValue", "Asc"}, {"m_pEntity.m_fFloatValue", "deSC"}}); 145 | 146 | // Assert 147 | return new EDF_TestResult( 148 | EDF_Test_DbEntitySortableEntitySingleWrapper.Cast(sorted.Get(0)).m_pEntity.m_fFloatValue == 50.3 && 149 | EDF_Test_DbEntitySortableEntitySingleWrapper.Cast(sorted.Get(1)).m_pEntity.m_fFloatValue == 5.2 && 150 | EDF_Test_DbEntitySortableEntitySingleWrapper.Cast(sorted.Get(2)).m_pEntity.m_fFloatValue == 1.1); 151 | } 152 | 153 | //------------------------------------------------------------------------------------------------ 154 | [Test("EDF_DbEntitySorterTests")] 155 | TestResultBase EDF_Test_DbEntitySorter_GetSorted_ArrayBool_AscSorted() 156 | { 157 | // Arrange 158 | array entities = { 159 | new EDF_Test_DbEntitySortableEntity(boolVal: false), 160 | new EDF_Test_DbEntitySortableEntity(boolVal: true), 161 | new EDF_Test_DbEntitySortableEntity(boolVal: false) 162 | }; 163 | 164 | // Act 165 | array sorted = EDF_DbEntitySorter.GetSorted(entities, {{"m_bBoolValue", EDF_EDbEntitySortDirection.ASCENDING}}); 166 | 167 | // Assert 168 | return new EDF_TestResult( 169 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(0)).m_bBoolValue == false && 170 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(1)).m_bBoolValue == false && 171 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(2)).m_bBoolValue == true); 172 | } 173 | 174 | //------------------------------------------------------------------------------------------------ 175 | [Test("EDF_DbEntitySorterTests")] 176 | TestResultBase EDF_Test_DbEntitySorter_GetSorted_ArrayBool_DescSorted() 177 | { 178 | // Arrange 179 | array entities = { 180 | new EDF_Test_DbEntitySortableEntity(boolVal: false), 181 | new EDF_Test_DbEntitySortableEntity(boolVal: true), 182 | new EDF_Test_DbEntitySortableEntity(boolVal: false) 183 | }; 184 | 185 | // Act 186 | array sorted = EDF_DbEntitySorter.GetSorted(entities, {{"m_bBoolValue", EDF_EDbEntitySortDirection.DESCENDING}}); 187 | 188 | // Assert 189 | return new EDF_TestResult( 190 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(0)).m_bBoolValue == true && 191 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(1)).m_bBoolValue == false && 192 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(2)).m_bBoolValue == false); 193 | } 194 | 195 | //------------------------------------------------------------------------------------------------ 196 | [Test("EDF_DbEntitySorterTests")] 197 | TestResultBase EDF_Test_DbEntitySorter_GetSorted_ArrayString_AscSorted() 198 | { 199 | // Arrange 200 | array entities = { 201 | new EDF_Test_DbEntitySortableEntity(stringVal: "Hello World"), 202 | new EDF_Test_DbEntitySortableEntity(stringVal: "987 ZyaD"), 203 | new EDF_Test_DbEntitySortableEntity(stringVal: "876 AmrA"), 204 | new EDF_Test_DbEntitySortableEntity(stringVal: "Hello Arma") 205 | }; 206 | 207 | // Act 208 | array sorted = EDF_DbEntitySorter.GetSorted(entities, {{"m_sStringValue", EDF_EDbEntitySortDirection.ASCENDING}}); 209 | 210 | // Assert 211 | return new EDF_TestResult( 212 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(0)).m_sStringValue == "876 AmrA" && 213 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(1)).m_sStringValue == "987 ZyaD" && 214 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(2)).m_sStringValue == "Hello Arma" && 215 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(3)).m_sStringValue == "Hello World"); 216 | } 217 | 218 | //------------------------------------------------------------------------------------------------ 219 | [Test("EDF_DbEntitySorterTests")] 220 | TestResultBase EDF_Test_DbEntitySorter_GetSorted_ArrayString_DescSorted() 221 | { 222 | // Arrange 223 | array entities = { 224 | new EDF_Test_DbEntitySortableEntity(stringVal: "Hello World"), 225 | new EDF_Test_DbEntitySortableEntity(stringVal: "987 ZyaD"), 226 | new EDF_Test_DbEntitySortableEntity(stringVal: "876 AmrA"), 227 | new EDF_Test_DbEntitySortableEntity(stringVal: "Hello Arma") 228 | }; 229 | 230 | // Act 231 | array sorted = EDF_DbEntitySorter.GetSorted(entities, {{"m_sStringValue", EDF_EDbEntitySortDirection.DESCENDING}}); 232 | 233 | // Assert 234 | return new EDF_TestResult( 235 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(0)).m_sStringValue == "Hello World" && 236 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(1)).m_sStringValue == "Hello Arma" && 237 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(2)).m_sStringValue == "987 ZyaD" && 238 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(3)).m_sStringValue == "876 AmrA"); 239 | } 240 | 241 | //------------------------------------------------------------------------------------------------ 242 | [Test("EDF_DbEntitySorterTests")] 243 | TestResultBase EDF_Test_DbEntitySorter_GetSorted_ArrayVector_AscSorted() 244 | { 245 | // Arrange 246 | array entities = { 247 | new EDF_Test_DbEntitySortableEntity(vectorVal: Vector(1, 9, 1)), 248 | new EDF_Test_DbEntitySortableEntity(vectorVal: Vector(1, 2, 1)), 249 | new EDF_Test_DbEntitySortableEntity(vectorVal: Vector(0, 9, 9)), 250 | new EDF_Test_DbEntitySortableEntity(vectorVal: Vector(3, 0, 0)) 251 | }; 252 | 253 | // Act 254 | array sorted = EDF_DbEntitySorter.GetSorted(entities, {{"m_vVectorValue", EDF_EDbEntitySortDirection.ASCENDING}}); 255 | 256 | // Assert 257 | return new EDF_TestResult( 258 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(0)).m_vVectorValue == Vector(0, 9, 9) && 259 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(1)).m_vVectorValue == Vector(1, 2, 1) && 260 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(2)).m_vVectorValue == Vector(1, 9, 1) && 261 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(3)).m_vVectorValue == Vector(3, 0, 0)); 262 | } 263 | 264 | //------------------------------------------------------------------------------------------------ 265 | [Test("EDF_DbEntitySorterTests")] 266 | TestResultBase EDF_Test_DbEntitySorter_GetSorted_ArrayVector_DescSorted() 267 | { 268 | // Arrange 269 | array entities = { 270 | new EDF_Test_DbEntitySortableEntity(vectorVal: Vector(1, 9, 1)), 271 | new EDF_Test_DbEntitySortableEntity(vectorVal: Vector(1, 2, 1)), 272 | new EDF_Test_DbEntitySortableEntity(vectorVal: Vector(0, 9, 9)), 273 | new EDF_Test_DbEntitySortableEntity(vectorVal: Vector(3, 0, 0)) 274 | }; 275 | 276 | // Act 277 | array sorted = EDF_DbEntitySorter.GetSorted(entities, {{"m_vVectorValue", EDF_EDbEntitySortDirection.DESCENDING}}); 278 | 279 | // Assert 280 | return new EDF_TestResult( 281 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(0)).m_vVectorValue == Vector(3, 0, 0) && 282 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(1)).m_vVectorValue == Vector(1, 9, 1) && 283 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(2)).m_vVectorValue == Vector(1, 2, 1) && 284 | EDF_Test_DbEntitySortableEntity.Cast(sorted.Get(3)).m_vVectorValue == Vector(0, 9, 9)); 285 | } 286 | -------------------------------------------------------------------------------- /tests/Scripts/Game/EDF_DbEntityUtilsTests.c: -------------------------------------------------------------------------------- 1 | class EDF_DbEntityUtilsTests : TestSuite 2 | { 3 | //------------------------------------------------------------------------------------------------ 4 | [Step(EStage.Setup)] 5 | void Setup() 6 | { 7 | } 8 | 9 | //------------------------------------------------------------------------------------------------ 10 | [Step(EStage.TearDown)] 11 | void TearDown() 12 | { 13 | } 14 | } 15 | 16 | class EDF_Test_DbEntityUtilsSaveStruct : EDF_DbEntity 17 | { 18 | float m_fFloatValue; 19 | string m_sStringValue; 20 | } 21 | 22 | class EDF_Test_DbEntityUtilsOtherClassType 23 | { 24 | float m_fFloatValue; 25 | string m_sStringValue; 26 | 27 | //------------------------------------------------------------------------------------------------ 28 | /* private -- Hotfix for 1.0, do not ctor directly */ 29 | void EDF_Test_DbEntityUtilsOtherClassType(float floatValue, string stringValue); 30 | } 31 | 32 | //------------------------------------------------------------------------------------------------ 33 | [Test("EDF_DbEntityUtilsTests")] 34 | TestResultBase EDF_Test_DbEntityUtils_StructAutoCopy_ValidInput_MatchingOutput() 35 | { 36 | // Arrange 37 | EDF_Test_DbEntityUtilsSaveStruct saveStruct(); 38 | saveStruct.m_fFloatValue = 42.42; 39 | saveStruct.m_sStringValue = "Hello World"; 40 | 41 | EDF_Test_DbEntityUtilsOtherClassType otherClass = EDF_Test_DbEntityUtilsOtherClassType.Cast(String("EDF_Test_DbEntityUtilsOtherClassType").ToType().Spawn()); 42 | 43 | // Act 44 | EDF_DbEntityUtils.StructAutoCopy(saveStruct, otherClass); 45 | 46 | // Assert 47 | return new EDF_TestResult(otherClass && (otherClass.m_fFloatValue == saveStruct.m_fFloatValue) && (otherClass.m_sStringValue == saveStruct.m_sStringValue)); 48 | } 49 | 50 | //------------------------------------------------------------------------------------------------ 51 | [Test("EDF_DbEntityUtilsTests")] 52 | TestResultBase EDF_Test_DbEntityUtils_DeepCopy_ValidInput_MatchingOutput() 53 | { 54 | // Arrange 55 | EDF_Test_DbEntityUtilsSaveStruct saveStruct(); 56 | saveStruct.m_fFloatValue = 42.42; 57 | saveStruct.m_sStringValue = "Hello World"; 58 | 59 | // Act 60 | auto deepCopy = EDF_Test_DbEntityUtilsSaveStruct.Cast(EDF_DbEntityUtils.DeepCopy(saveStruct)); 61 | 62 | // Assert 63 | return new EDF_TestResult(deepCopy && (deepCopy.m_fFloatValue == saveStruct.m_fFloatValue) && (deepCopy.m_sStringValue == saveStruct.m_sStringValue)); 64 | } 65 | -------------------------------------------------------------------------------- /tests/Scripts/Game/EDF_DbFindConditionBuilderTests.c: -------------------------------------------------------------------------------- 1 | class EDF_DbFindConditionBuilderTests : TestSuite 2 | { 3 | //------------------------------------------------------------------------------------------------ 4 | [Step(EStage.Setup)] 5 | void Setup() 6 | { 7 | } 8 | 9 | //------------------------------------------------------------------------------------------------ 10 | [Step(EStage.TearDown)] 11 | void TearDown() 12 | { 13 | } 14 | } 15 | 16 | //------------------------------------------------------------------------------------------------ 17 | [Test("EDF_DbFindConditionBuilderTests")] 18 | TestResultBase EDF_Test_DbFindConditionBuilder_And_EmptyArgs_EmptyCondition() 19 | { 20 | // Act 21 | EDF_DbFindAnd condition = EDF_DbFind.And({}); 22 | 23 | // Assert 24 | return new EDF_TestResult(condition.m_aConditions.Count() == 0); 25 | } 26 | 27 | //------------------------------------------------------------------------------------------------ 28 | [Test("EDF_DbFindConditionBuilderTests")] 29 | TestResultBase EDF_Test_DbFindConditionBuilder_And_MultipleConditions_MultipleWrapped() 30 | { 31 | // Act 32 | EDF_DbFindAnd condition = EDF_DbFind.And({new EDF_DbFindCondition(), new EDF_DbFindCondition()}); 33 | 34 | // Assert 35 | return new EDF_TestResult(condition.m_aConditions.Count() == 2); 36 | } 37 | 38 | //------------------------------------------------------------------------------------------------ 39 | [Test("EDF_DbFindConditionBuilderTests")] 40 | TestResultBase EDF_Test_DbFindConditionBuilder_Or_EmptyArgs_EmptyCondition() 41 | { 42 | // Act 43 | EDF_DbFindOr condition = EDF_DbFind.Or({}); 44 | 45 | // Assert 46 | return new EDF_TestResult(condition.m_aConditions.Count() == 0); 47 | } 48 | 49 | //------------------------------------------------------------------------------------------------ 50 | [Test("EDF_DbFindConditionBuilderTests")] 51 | TestResultBase EDF_Test_DbFindConditionBuilder_Or_MultipleConditions_MultipleWrapped() 52 | { 53 | // Act 54 | EDF_DbFindOr condition = EDF_DbFind.Or({new EDF_DbFindCondition(), new EDF_DbFindCondition()}); 55 | 56 | // Assert 57 | return new EDF_TestResult(condition.m_aConditions.Count() == 2); 58 | } 59 | 60 | //------------------------------------------------------------------------------------------------ 61 | [Test("EDF_DbFindConditionBuilderTests")] 62 | TestResultBase EDF_Test_DbFindConditionBuilder_Field_SingleField_ValidBuilder() 63 | { 64 | // Act 65 | EDF_DbFindFieldCollectionHandlingBuilder builder = EDF_DbFind.Field("fieldName"); 66 | 67 | // Assert 68 | return new EDF_TestResult(builder.m_sFieldPath == "fieldName" && builder.m_bInverted == false); 69 | } 70 | 71 | //------------------------------------------------------------------------------------------------ 72 | [Test("EDF_DbFindConditionBuilderTests")] 73 | TestResultBase EDF_Test_DbFindConditionBuilder_Field_SingleFieldMultiValueInverted_ValidBuilder() 74 | { 75 | // Act 76 | EDF_DbFindFieldAllValueConditonBuilder builder = EDF_DbFind.Field("fieldName.subField").Not(); 77 | 78 | // Assert 79 | return new EDF_TestResult(builder.m_sFieldPath == "fieldName.subField" && builder.m_bInverted == true); 80 | } 81 | 82 | //------------------------------------------------------------------------------------------------ 83 | [Test("EDF_DbFindConditionBuilderTests")] 84 | TestResultBase EDF_Test_DbFindConditionBuilder_Field_MultiField_FieldsChained() 85 | { 86 | // Act 87 | EDF_DbFindFieldCollectionHandlingBuilder builder = EDF_DbFind.Field("fieldName").Field("subField"); 88 | 89 | // Assert 90 | return new EDF_TestResult(builder.m_sFieldPath == "fieldName.subField"); 91 | } 92 | 93 | //------------------------------------------------------------------------------------------------ 94 | [Test("EDF_DbFindConditionBuilderTests")] 95 | TestResultBase EDF_Test_DbFindConditionBuilder_Field_Length_ModifierPresent() 96 | { 97 | // Act 98 | EDF_DbFindFieldNumericValueConditonBuilder builder = EDF_DbFind.Field("stringField").Length(); 99 | 100 | // Assert 101 | return new EDF_TestResult(builder.m_sFieldPath.EndsWith("stringField" + EDF_DbFindFieldAnnotations.LENGTH)); 102 | } 103 | 104 | //------------------------------------------------------------------------------------------------ 105 | [Test("EDF_DbFindConditionBuilderTests")] 106 | TestResultBase EDF_Test_DbFindConditionBuilder_Field_Count_ModifierPresent() 107 | { 108 | // Act 109 | EDF_DbFindFieldNumericValueConditonBuilder builder = EDF_DbFind.Field("collectionName").Count(); 110 | 111 | // Assert 112 | return new EDF_TestResult(builder.m_sFieldPath.EndsWith("collectionName:count")); 113 | } 114 | 115 | //------------------------------------------------------------------------------------------------ 116 | [Test("EDF_DbFindConditionBuilderTests")] 117 | TestResultBase EDF_Test_DbFindConditionBuilder_Field_Any_ModifierPresent() 118 | { 119 | // Act 120 | EDF_DbFindFieldMainConditionBuilder builder = EDF_DbFind.Field("collectionName").Any(); 121 | 122 | // Assert 123 | return new EDF_TestResult(builder.m_sFieldPath.EndsWith(":any")); 124 | } 125 | 126 | //------------------------------------------------------------------------------------------------ 127 | [Test("EDF_DbFindConditionBuilderTests")] 128 | TestResultBase EDF_Test_DbFindConditionBuilder_Field_All_ModifierPresent() 129 | { 130 | // Act 131 | EDF_DbFindFieldMainConditionBuilder builder = EDF_DbFind.Field("collectionName").All(); 132 | 133 | // Assert 134 | return new EDF_TestResult(builder.m_sFieldPath.EndsWith(":all")); 135 | } 136 | 137 | //------------------------------------------------------------------------------------------------ 138 | [Test("EDF_DbFindConditionBuilderTests")] 139 | TestResultBase EDF_Test_DbFindConditionBuilder_Field_Keys_ModifierPresent() 140 | { 141 | // Act 142 | EDF_DbFindFieldBasicCollectionHandlingBuilder builder = EDF_DbFind.Field("collectionName").Keys(); 143 | 144 | // Assert 145 | return new EDF_TestResult(builder.m_sFieldPath.EndsWith(":keys")); 146 | } 147 | 148 | //------------------------------------------------------------------------------------------------ 149 | [Test("EDF_DbFindConditionBuilderTests")] 150 | TestResultBase EDF_Test_DbFindConditionBuilder_Field_Values_ModifierPresent() 151 | { 152 | // Act 153 | EDF_DbFindFieldBasicCollectionHandlingBuilder builder = EDF_DbFind.Field("collectionName").Values(); 154 | 155 | // Assert 156 | return new EDF_TestResult(builder.m_sFieldPath.EndsWith(":values")); 157 | } 158 | 159 | //------------------------------------------------------------------------------------------------ 160 | [Test("EDF_DbFindConditionBuilderTests")] 161 | TestResultBase EDF_Test_DbFindConditionBuilder_Field_ValuesAny_ModifiersPresent() 162 | { 163 | // Act 164 | EDF_DbFindFieldMainConditionBuilder builder = EDF_DbFind.Field("collectionName").Values().Any(); 165 | 166 | // Assert 167 | return new EDF_TestResult(builder.m_sFieldPath.EndsWith(":values:any")); 168 | } 169 | 170 | //------------------------------------------------------------------------------------------------ 171 | [Test("EDF_DbFindConditionBuilderTests")] 172 | TestResultBase EDF_Test_DbFindConditionBuilder_Field_At_IndexFieldSet() 173 | { 174 | // Act 175 | EDF_DbFindFieldMainConditionBuilder builder = EDF_DbFind.Field("collectionName").At(3); 176 | 177 | // Assert 178 | return new EDF_TestResult(builder.m_sFieldPath == "collectionName.{3}"); 179 | } 180 | 181 | //------------------------------------------------------------------------------------------------ 182 | [Test("EDF_DbFindConditionBuilderTests")] 183 | TestResultBase EDF_Test_DbFindConditionBuilder_Field_OfType_ModiferAndTypefilterPresent() 184 | { 185 | // Act 186 | EDF_DbFindFieldMainConditionBuilder builder = EDF_DbFind.Field("collectionName").OfType(EDF_DbFind); 187 | 188 | // Assert 189 | return new EDF_TestResult(builder.m_sFieldPath == "collectionName.{EDF_DbFind}"); 190 | } 191 | 192 | //------------------------------------------------------------------------------------------------ 193 | [Test("EDF_DbFindConditionBuilderTests")] 194 | class EDF_Test_DbFindConditionBuilder_Field_ComplexBuild_DebugStringEqual : TestBase 195 | { 196 | [Step(EStage.Main)] 197 | void DoTest() 198 | { 199 | // Arrange 200 | EDF_DbFindCondition condition = EDF_DbFind.Or({ 201 | EDF_DbFind.Field("A").Not().NullOrDefault(), 202 | EDF_DbFind.Field("B").NullOrDefault(), 203 | EDF_DbFind.And({ 204 | EDF_DbFind.Field("CString").Contains("SubString"), 205 | EDF_DbFind.Field("DBoolArray").Equals({true, false, true, true}), 206 | EDF_DbFind.And({ 207 | EDF_DbFind.Field("E.m_Numbers").Contains(100), 208 | EDF_DbFind.Field("F.m_ComplexWrapperSet").OfType(EDF_DbFind).Any().Field("someNumber").Not().EqualsAnyOf({1, 2}) 209 | }), 210 | EDF_DbFind.Or({ 211 | EDF_DbFind.Field("G").EqualsAnyOf({12, 13}) 212 | }) 213 | }) 214 | }); 215 | 216 | // Act 217 | string debugString = condition.GetDebugString(); 218 | //Print(debugString); 219 | debugString.Replace("\t", ""); 220 | debugString.Replace(" ", ""); 221 | 222 | // Assert 223 | string compareString = "Or(\ 224 | CheckNullOrDefault(fieldPath:'A', shouldBeNullOrDefault:false), \ 225 | CheckNullOrDefault(fieldPath:'B', shouldBeNullOrDefault:true), \ 226 | And(\ 227 | Compare(fieldPath:'CString', operator:CONTAINS, values:{'SubString'}), \ 228 | Compare(fieldPath:'DBoolArray', operator:EQUAL, values:{{true, false, true, true}}), \ 229 | And(\ 230 | Compare(fieldPath:'E.m_Numbers', operator:CONTAINS, values:{100}), \ 231 | Compare(fieldPath:'F.m_ComplexWrapperSet.{EDF_DbFind}:any.someNumber', operator:NOT_EQUAL, values:{1, 2})\ 232 | ), \ 233 | Or(\ 234 | Compare(fieldPath:'G', operator:EQUAL, values:{12, 13})\ 235 | )\ 236 | )\ 237 | )"; 238 | 239 | compareString.Replace("\r", ""); 240 | compareString.Replace("\t", ""); 241 | compareString.Replace(" ", ""); 242 | 243 | SetResult(new EDF_TestResult(debugString == compareString)); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /tests/Scripts/Game/EDF_HexHelperTests.c: -------------------------------------------------------------------------------- 1 | class EDF_HexHelperTests : TestSuite 2 | { 3 | //------------------------------------------------------------------------------------------------ 4 | [Step(EStage.Setup)] 5 | void Setup() 6 | { 7 | } 8 | 9 | //------------------------------------------------------------------------------------------------ 10 | [Step(EStage.TearDown)] 11 | void TearDown() 12 | { 13 | } 14 | } 15 | 16 | //------------------------------------------------------------------------------------------------ 17 | [Test("EDF_HexHelperTests")] 18 | TestResultBase EDF_Test_Utils_IntToHex_ZeroNoPadding_Zero() 19 | { 20 | return new EDF_TestResult(EDF_HexHelper.Convert(0) == "0"); 21 | } 22 | 23 | //------------------------------------------------------------------------------------------------ 24 | [Test("EDF_HexHelperTests")] 25 | TestResultBase EDF_Test_Utils_IntToHex_ZeroPadding4_Zero4() 26 | { 27 | return new EDF_TestResult(EDF_HexHelper.Convert(0, fixedLength: 4) == "0000"); 28 | } 29 | 30 | //------------------------------------------------------------------------------------------------ 31 | [Test("EDF_HexHelperTests")] 32 | TestResultBase EDF_Test_Utils_IntToHex_ZeroPadding10_Zero8() 33 | { 34 | return new EDF_TestResult(EDF_HexHelper.Convert(0, fixedLength: 10) == "00000000"); 35 | } 36 | 37 | //------------------------------------------------------------------------------------------------ 38 | [Test("EDF_HexHelperTests")] 39 | TestResultBase EDF_Test_Utils_IntToHex_1000_3e8() 40 | { 41 | return new EDF_TestResult(EDF_HexHelper.Convert(1000) == "3e8"); 42 | } 43 | 44 | //------------------------------------------------------------------------------------------------ 45 | [Test("EDF_HexHelperTests")] 46 | TestResultBase EDF_Test_Utils_IntToHex_1000Upper_3E8() 47 | { 48 | return new EDF_TestResult(EDF_HexHelper.Convert(1000, true) == "3E8"); 49 | } 50 | 51 | //------------------------------------------------------------------------------------------------ 52 | [Test("EDF_HexHelperTests")] 53 | TestResultBase EDF_Test_Utils_IntToHex_13342UpperPadded_00341E() 54 | { 55 | return new EDF_TestResult(EDF_HexHelper.Convert(13342, true, 6) == "00341E"); 56 | } 57 | 58 | //------------------------------------------------------------------------------------------------ 59 | [Test("EDF_HexHelperTests")] 60 | TestResultBase EDF_Test_Utils_IntToHex_IntMaxUpper_7FFFFFFF() 61 | { 62 | return new EDF_TestResult(EDF_HexHelper.Convert(int.MAX, true) == "7FFFFFFF"); 63 | } 64 | 65 | //------------------------------------------------------------------------------------------------ 66 | [Test("EDF_HexHelperTests")] 67 | TestResultBase EDF_Test_Utils_IntToHex_MiddleZeros_b00b() 68 | { 69 | return new EDF_TestResult(EDF_HexHelper.Convert(45067) == "b00b"); 70 | } 71 | 72 | //------------------------------------------------------------------------------------------------ 73 | [Test("EDF_HexHelperTests")] 74 | TestResultBase EDF_Test_Utils_IntToHex_MiddleZerosPadded_00b00b() 75 | { 76 | return new EDF_TestResult(EDF_HexHelper.Convert(45067, fixedLength: 7) == "000b00b"); 77 | } 78 | 79 | //------------------------------------------------------------------------------------------------ 80 | [Test("EDF_HexHelperTests")] 81 | TestResultBase EDF_Test_Utils_IntToHex_FixedExeeded_AllPresent() 82 | { 83 | return new EDF_TestResult(EDF_HexHelper.Convert(4095, fixedLength: 2) == "fff"); 84 | } 85 | -------------------------------------------------------------------------------- /tests/Scripts/Game/Setup/EDF_AutoTestEntity.c: -------------------------------------------------------------------------------- 1 | class EDF_AutoTestEntityClass : GenericEntityClass 2 | { 3 | }; 4 | 5 | class EDF_AutoTestEntity : GenericEntity 6 | { 7 | //------------------------------------------------------------------------------------------------ 8 | protected void EDF_AutoTestEntity(IEntitySource src, IEntity parent) 9 | { 10 | if (!GetWorld() || GetWorld().IsEditMode()) 11 | return; 12 | 13 | Run(); 14 | 15 | GetGame().RequestClose(); 16 | } 17 | 18 | //------------------------------------------------------------------------------------------------ 19 | static void Run() 20 | { 21 | // Run tests and wait until finished 22 | TestHarness.Begin(); 23 | int start = System.GetTickCount(); 24 | while (!TestHarness.Run() && (System.GetTickCount() - start) < 10000) 25 | { 26 | } 27 | TestHarness.End(); 28 | 29 | // Get and process results 30 | string testResults = TestHarness.Report(); 31 | 32 | int year, month, day, hour, minute, second; 33 | System.GetYearMonthDayUTC(year, month, day); 34 | System.GetHourMinuteSecondUTC(hour, minute, second); 35 | string resultFile = string.Format("TestResults/Run %1-%2-%3 %4_%5_%6.xml", year, month, day, hour, minute, second); 36 | 37 | if (!testResults.Contains("Failed"; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /tests/Worlds/Layers/default.layer: -------------------------------------------------------------------------------- 1 | EDF_AutoTestEntity { 2 | coords 909 42 2646 3 | } -------------------------------------------------------------------------------- /tests/Worlds/TestWorld.ent: -------------------------------------------------------------------------------- 1 | SubScene { 2 | Parent "{C7DC72AF0AE17724}worlds/Arland/EmptyArland.ent" 3 | } -------------------------------------------------------------------------------- /tests/Worlds/TestWorld.ent.meta: -------------------------------------------------------------------------------- 1 | MetaFileClass { 2 | Name "{F5E250CFD54DEC2E}Worlds/TestWorld.ent" 3 | Configurations { 4 | ENTResourceClass PC { 5 | } 6 | ENTResourceClass XBOX_ONE : PC { 7 | } 8 | ENTResourceClass XBOX_SERIES : PC { 9 | } 10 | ENTResourceClass PS4 : PC { 11 | } 12 | ENTResourceClass HEADLESS : PC { 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /tests/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arkensor/EnfusionDatabaseFramework/20c0fbf251fae89446a61fa511649850c29c0b52/tests/thumbnail.png --------------------------------------------------------------------------------