├── .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 |
5 |
6 |
7 | [](https://github.com/Arkensor/EnfusionDatabaseFramework/releases)
8 | [](https://reforger.armaplatform.com/workshop/5D6EA74A94173EDF)
9 | [](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
--------------------------------------------------------------------------------
]