├── .gitignore ├── Config └── FilterPlugin.ini ├── LICENSE ├── MathVM.uplugin ├── README.md ├── Resources └── Icon128.png └── Source └── MathVM ├── MathVM.Build.cs ├── Private ├── MathVM.cpp ├── MathVMBlueprintFunctionLibrary.cpp ├── MathVMBuiltinFunctions.cpp ├── MathVMClasses.cpp ├── MathVMCompiler.cpp ├── MathVMResourceObject.cpp ├── MathVMResources.cpp ├── MathVMRuntime.cpp ├── MathVMTokenizer.cpp └── Tests │ ├── MathVMBuiltinFunctionsTests.cpp │ ├── MathVMResourcesTests.cpp │ └── MathVMTests.cpp └── Public ├── MathVM.h ├── MathVMBlueprintFunctionLibrary.h ├── MathVMBuiltinFunctions.h ├── MathVMResourceObject.h └── MathVMResources.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | .vsconfig 4 | 5 | # Compiled Object files 6 | *.slo 7 | *.lo 8 | *.o 9 | *.obj 10 | 11 | # Precompiled Headers 12 | *.gch 13 | *.pch 14 | 15 | # Compiled Dynamic libraries 16 | *.so 17 | *.dylib 18 | *.dll 19 | 20 | # Fortran module files 21 | *.mod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | *.ipa 34 | 35 | # These project files can be generated by the engine 36 | *.xcodeproj 37 | *.xcworkspace 38 | *.sln 39 | *.suo 40 | *.opensdf 41 | *.sdf 42 | *.VC.db 43 | *.VC.opendb 44 | 45 | # Precompiled Assets 46 | SourceArt/**/*.png 47 | SourceArt/**/*.tga 48 | 49 | # Binary Files 50 | Binaries/* 51 | Plugins/*/Binaries/* 52 | 53 | # Builds 54 | Build/* 55 | 56 | # Whitelist PakBlacklist-.txt files 57 | !Build/*/ 58 | Build/*/** 59 | !Build/*/PakBlacklist*.txt 60 | 61 | # Don't ignore icon files in Build 62 | !Build/**/*.ico 63 | 64 | # Built data for maps 65 | *_BuiltData.uasset 66 | 67 | # Configuration files generated by the Editor 68 | Saved/* 69 | 70 | # Compiled source files for the engine to use 71 | Intermediate/* 72 | 73 | # Cache files for the editor to use 74 | DerivedDataCache/* 75 | 76 | # Custom ignores 77 | Plugins/* 78 | Releases/* 79 | -------------------------------------------------------------------------------- /Config/FilterPlugin.ini: -------------------------------------------------------------------------------- 1 | [FilterPlugin] 2 | /LICENSE 3 | /README.md 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Roberto De Ioris 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 | -------------------------------------------------------------------------------- /MathVM.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "20250604", 5 | "FriendlyName": "MathVM", 6 | "Description": "Math Expressions Evaluator", 7 | "Category": "Other", 8 | "CreatedBy": "Roberto De Ioris", 9 | "CreatedByURL": "https://github.com/rdeioris", 10 | "DocsURL": "https://github.com/rdeioris/MathVM/blob/master/README.md", 11 | "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/e3daa8685f514f9e948acc5f2dadd0ae", 12 | "SupportURL": "https://discord.gg/WJkRY2UVhn", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": true, 17 | "Modules": [ 18 | { 19 | "Name": "MathVM", 20 | "Type": "Runtime", 21 | "LoadingPhase": "Default", 22 | "WhitelistPlatforms": [ "Win64", "Mac", "Linux", "Android", "IOS" ] 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MathVM 2 | ![MathVM](https://github.com/rdeioris/MathVM/assets/2234592/0be2bda5-6b79-4979-9e87-3c848d5a4811) 3 | 4 | Unreal Engine plugin for (parallel) Math Expressions evaluation and plotting 5 | 6 | ![image](https://github.com/rdeioris/MathVM/assets/2234592/929f682e-7ec7-4caa-b70f-781d0fb16f01) 7 | 8 | ![image](https://github.com/rdeioris/MathVM/assets/2234592/5c891201-d0bd-4127-bc08-80752761ac60) 9 | 10 | ![image](https://github.com/rdeioris/MathVM/assets/2234592/c91bdfa0-76fc-4cef-8029-afafee432bf0) 11 | 12 | Join the Discord Server: https://discord.gg/WJkRY2UVhn 13 | 14 | Buy it from the Marketplace for supporting the development: https://www.unrealengine.com/marketplace/en-US/product/mathvm 15 | 16 | Support the project with Patreon: https://www.patreon.com/rdeioris 17 | 18 | Commercial support is provided by Blitter S.r.l. (Italy) [contact the Discord Server Admin for infos] 19 | 20 | ## What is it? 21 | This plugins exposes a simple framework (both C++ and Blueprint) for running user-defined math expressions: 22 | 23 | ``` 24 | y = sin(x) * 2 25 | ``` 26 | 27 | This expression will compute the sin of x and multiply it by 2. The result will be put in y. 28 | 29 | The two symbols (x and y), can be `local` variables or `globals` (more on this below) and, in the case of x, it can be a `constant`. 30 | 31 | There is a very important difference between `local` and `global`: MathVM supports running expressions in parallel (read: on multiple threads) but while accessing local variables is fully thread-safe and lock-free (each 32 | thread works on a different copy of them), global variables are shared between parallel evaluations. For global variables a locking is required to avoid race conditions (see below). Constants are shared and lock-free (but obviously you cannot change them). 33 | 34 | The native data type is double and multiple statements can be specified by using the ```;``` separator: 35 | 36 | ``` 37 | y = tan(x * 2); y = y + (3 * sin(z)); final = y + x; 38 | ``` 39 | 40 | The language has no support for loops or conditionals, this increases safety for external provided code and keeps the VM very simple and efficient (technically it is an extended Shunting Yard https://en.wikipedia.org/wiki/Shunting_yard_algorithm). 41 | 42 | ## Comments 43 | 44 | You can add comments in your expressions using the # symbol. 45 | 46 | Comments can be per-line: 47 | 48 | ``` 49 | y = sin(x); 50 | # this is a comment 51 | z = cos(w); 52 | # this is another comment 53 | ``` 54 | 55 | or in-line 56 | 57 | ``` 58 | y = sin(x); # compute the sin of x # z = cos(w); # this is another comment # xyz = 100; 59 | ``` 60 | 61 | ## The Blueprint API 62 | 63 | ### MathVMRunSimple() 64 | 65 | ```cpp 66 | static bool MathVMRunSimple(const FString& Code, UPARAM(ref) TMap& LocalVariables, const TArray& Resources, double& Result, FString& Error); 67 | ``` 68 | 69 | This is the simplest node with support for local variables, resources (see below) and a single return value 70 | 71 | ![image](https://github.com/rdeioris/MathVM/assets/2234592/8523d66f-12af-4fa5-bb6c-00025ed431e1) 72 | 73 | ### MathVMRunSimpleMulti() 74 | 75 | ```cpp 76 | bool MathVMRunSimpleMulti(const FString& Code, UPARAM(ref) TMap& LocalVariables, const TArray& Resources, const int32 PopResults, TArray& Results, FString& Error) 77 | ```` 78 | 79 | This is a variant of the simple node supporting multiple return values (by specifying the amoutn of results to pop) 80 | 81 | ![image](https://github.com/rdeioris/MathVM/assets/2234592/892ea32a-d131-402f-ae7b-167f910a061a) 82 | 83 | ### MathVMRun() 84 | 85 | ```cpp 86 | static void MathVMRun(const FString& Code, const TMap& GlobalVariables, const TMap& Constants, const TArray& Resources, const FMathVMEvaluatedWithResult& OnEvaluated, const int32 NumSamples = 1, const FString& SampleLocalVariable = "i"); 87 | ``` 88 | This is the full-featured function supporting parallel execution (by specifying the number of 'samples'). The 'SampleLocalVariable' specifies the name of the local variable that will get the current SampleId (so you can recognize each iteration by that value). Internally, ParallelFor is used, that means the tasks 89 | will be distributed among various threads (generally based on the number of available cpu cores) 90 | 91 | ![image](https://github.com/rdeioris/MathVM/assets/2234592/f643dabc-c061-4c7d-877c-eac12ad95505) 92 | 93 | 94 | Note: The braces in the code are used for locking (see the parallel execution section below) 95 | 96 | ### MathVMPlotter() 97 | 98 | ```cpp 99 | static void MathVMPlotter(UObject* WorldContextObject, const FString& Code, const int32 NumSamples, const TMap& VariablesToPlot, const TArray& TextsToPlot, const TMap& Constants, const TMap& GlobalVariables, const TArray& Resources, const FMathVMPlotGenerated& OnPlotGenerated, const FMathVMPlotterConfig& PlotterConfig, const double DomainMin = 0, const double DomainMax = 1, const FString& SampleLocalVariable = "i"); 100 | ``` 101 | 102 | This follows the same logic of MathVMRun() but plots lines and points in a texture, based on the expressions results. 103 | 104 | ![image](https://github.com/rdeioris/MathVM/assets/2234592/dbc23085-6d1b-4735-b997-f18f7e45ea89) 105 | 106 | The VariablesToPlot map allows you to define how to draw each sampled value (currently you can draw lines, points and a combination of two with custom thickness). 107 | 108 | By default a 1024x1024 texture (a RenderTarget) will be returned, but you can pass an already existent render target using the PlotterConfig structure: 109 | 110 | ![image](https://github.com/rdeioris/MathVM/assets/2234592/fb8feceb-f022-4f23-842a-f73a3da7ef6b) 111 | 112 | The structure allows to define background, borders and grid properties of the plot too. 113 | 114 | This is an example result for 8 samples, two variables (x and y), domain (-1, 1) and a red grid: 115 | 116 | ``` 117 | y = 0; 118 | x = 1 / (i + 1); 119 | ``` 120 | 121 | ![image](https://github.com/rdeioris/MathVM/assets/2234592/481e39e3-96a3-46e5-aebf-3a6a6ca86cd3) 122 | 123 | You can add text to the plot by adding items to the TextsToPlot array: 124 | 125 | ![image](https://github.com/rdeioris/MathVM/assets/2234592/43ff359a-8226-4e4e-99ad-74cfd62c410d) 126 | 127 | You can put global variables in the text by surrounding them in braces. 128 | 129 | ## The C++ API 130 | 131 | The ```FMathVM``` class implements a full-featured VM for executing basic math and trigonometry operations. Once you have an instance you can assign Globals, Consts or Resources (see below): 132 | 133 | ```cpp 134 | #include "MathVM.h" // Remember to add MathVM module in your Build.cs file! 135 | ... 136 | 137 | FMathVM MathVM; 138 | MathVM.RegisterGlobalVariable("hello", 100); 139 | MathVM.RegisterConst("XYZ", 200); 140 | ``` 141 | 142 | You can then tokenize and compile the code: 143 | 144 | ```cpp 145 | const bool bSuccess = MathVM.TokenizeAndCompile("y = sin(x)"); 146 | if (!bSuccess) 147 | { 148 | // you can access the tokenization and compilation error FString with MathVM.GetError() 149 | } 150 | ``` 151 | 152 | Running is accomplished with the ```Execute``` family of methods: 153 | 154 | ```cpp 155 | // execute the compiled code passing the specified local variables and popping the specified number of results. In case of error, the method will return false and will put the message in the Error reference 156 | bool Execute(TMap& LocalVariables, const int32 PopResults, TArray& Results, FString& Error, void* LocalContext = nullptr); 157 | 158 | // like Execute(), but discards the return values 159 | bool ExecuteAndDiscard(TMap& LocalVariables, FString& Error, void* LocalContext = nullptr); 160 | 161 | // like Execute() but pops a single return value 162 | bool ExecuteOne(TMap& LocalVariables, double& Result, FString& Error, void* LocalContext = nullptr); 163 | 164 | // like Execute() but ignores return values and Error message. (Useful for testing) 165 | bool ExecuteStealth(TMap& LocalVariables, void* LocalContext = nullptr); 166 | ``` 167 | 168 | ## Parrallel evaluation (A.K.A. critical sections) 169 | 170 | If there are parts of your expressions that works over global variables, and you want to avoid race conditions you can "surround" critical sections with curly brackets (braces): 171 | 172 | ``` 173 | y = sin(z); {x = x + 1;} 174 | ``` 175 | 176 | Here the x increment (assuming x is a global variable) will be under lock. 177 | 178 | Note: the compiler will automatically detect deadlocks 179 | 180 | From C++ you can make use of the ParallelFor() function: 181 | 182 | ```cpp 183 | ParallelFor(100, [&](const int32 ThreadId) 184 | { 185 | TMap LocalVariables; 186 | LocalVariables.Add("i", ThreadId); 187 | MathVM->ExecuteStealth(LocalVariables); 188 | }); 189 | ``` 190 | 191 | This will try to distribute 100 evalutations by setting the "i" local variable for each one. 192 | 193 | Generally every Execute() method is thread safe so you can use it safely in Async tasks. 194 | 195 | ## Resources 196 | 197 | Resources are blocks of data that can be read and written by a MathVM instance. Currently Textures, Curves, DataTables and Array of doubles are supported, but you can implement your own in C++ by implementing the ```IMathVMResource``` interface. 198 | 199 | To access those data from your expressions you can use the read() and write() functions: 200 | 201 | ``` 202 | value = read(id, ...); 203 | write(id, ...); 204 | ``` 205 | 206 | In addition to `id` (the index of the provided resource), they have variable number of arguments as each Resource type has a different way of indexing data: 207 | 208 | ### Texture2D (readonly) 209 | 210 | ``` 211 | value = read(id, channel, u, v) 212 | ``` 213 | 214 | `channel` is the color channel (0 for r, 1 for g, 2 for b, 3 for a) u and v are the UV coordinates of the texture sample you want to read (no filtering is applied). 215 | 216 | Currently only RGBA and BGRA no mips textures are supported. Every other type of texture will return a 0. 217 | 218 | You can create a Texture2D Resource with 219 | 220 | ```cpp 221 | static UMathVMResourceObject* MathVMResourceObjectFromTexture2D(UTexture2D* Texture); 222 | MakeShared(Texture); // C++ only version 223 | ``` 224 | 225 | ### Curve (readonly) 226 | 227 | ``` 228 | value = read(id, channel, t) 229 | ``` 230 | 231 | ```channel``` is the index of the RichCurve in the Curve asset. (0 for a simple float Curve, between 0 and 3 for color curve). 232 | 233 | ```t``` is the 'time' at which the curve is sampled. 234 | 235 | You can create a Curve Resource with 236 | 237 | ```cpp 238 | static UMathVMResourceObject* MathVMResourceObjectFromCurveBase(UCurveBase* Curve); 239 | MakeShared(Curve); // C++ only version 240 | ``` 241 | 242 | ### DataTable (reaonly) 243 | 244 | ``` 245 | value = read(id, row, column) 246 | ``` 247 | 248 | Get a double value from the specified row and column of the DataTable. 249 | 250 | Only numeric columns are supported. You need to specify which columns you want to import when creating the Resource: 251 | 252 | ```cpp 253 | static UMathVMResourceObject* MathVMResourceObjectFromDataTable(UDataTable* DataTable, const TArray& FieldNames); 254 | MakeShared(DataTable, FieldNames); // C++ only version 255 | ``` 256 | 257 | ### Array of doubles (read/write) 258 | 259 | ``` 260 | value = read(id, index); 261 | write(id, index, value); 262 | ``` 263 | 264 | The simples of all the provided Resources. You just specify the `index` of the array you are interested in. 265 | 266 | You can create Array of doubles Resources with: 267 | 268 | ```cpp 269 | static UMathVMResourceObject* MathVMResourceObjectAsDoubleArray(const int32 ArraySize); 270 | MakeShared(ArraySize); // C++ only version 271 | ``` 272 | 273 | ## Plotting 274 | 275 | Plotting is currently sopported only via the blueprint function ```MathVMPlotter()```. While you can obviously use it from C++, a more advanced api (with SVG support) is in development. 276 | 277 | Points, lines and texts are the only plottable objects. More shapes and graph types (like bars and pies) are in development. 278 | 279 | ## Adding functions to the VM 280 | 281 | You can extend an FMathVM instance using the method 282 | 283 | ```cpp 284 | bool RegisterFunction(const FString& Name, FMathVMFunction Callable, const int32 NumArgs) 285 | ``` 286 | 287 | The ```FMathVMFunction``` represents the signature of the function: 288 | 289 | ```cpp 290 | TFunction& Args)> 291 | ``` 292 | 293 | A bunch of macros are available for quick function definitions. 294 | 295 | You can define a new function using a lambda: 296 | 297 | ```cpp 298 | MathVM.RegisterFunction("sin2", MATHVM_LAMBDA 299 | { 300 | MATHVM_RETURN(FMath::Sin(Args[0])); 301 | }, 1); 302 | ``` 303 | 304 | This corresponds to: 305 | 306 | ```cpp 307 | MathVM.RegisterFunction("sin2", [](FMathVMCallContext& CallContext, const TArray& Args) -> bool 308 | { 309 | return CallContext.PushResult(FMath::Sin(Args[0]))); 310 | }, 1); 311 | ``` 312 | 313 | The FMathVMCallContext object contains the Stack of the current execution as well as local variables and a LocalContext (it is a void pointer that can be passed by the various Execute() functions). 314 | 315 | By passing -1 to the NumberOfArgs argument in RegisterFunction(), you can support variable number of arguments. 316 | 317 | This is the implementation of the `all(...)` function: 318 | 319 | ```cpp 320 | bool All(MATHVM_ARGS) 321 | { 322 | for (const double Value : Args) 323 | { 324 | if (Value == 0.0) 325 | { 326 | MATHVM_RETURN(0); 327 | } 328 | } 329 | MATHVM_RETURN(1); 330 | } 331 | ``` 332 | 333 | While this one (with error check and reporting) is for `equal(...)`: 334 | 335 | ```cpp 336 | bool Equal(MATHVM_ARGS) 337 | { 338 | if (Args.Num() < 2) 339 | { 340 | MATHVM_ERROR("equal expects at least 2 arguments"); 341 | } 342 | 343 | for (int32 ArgIndex = 1; ArgIndex < Args.Num(); ArgIndex++) 344 | { 345 | if (Args[ArgIndex] != Args[0]) 346 | { 347 | MATHVM_RETURN(0); 348 | } 349 | } 350 | 351 | MATHVM_RETURN(1); 352 | } 353 | ``` 354 | 355 | ## Unit Tests 356 | 357 | The plugin has extensive code coverage available into ```Source/MathVM/Private/Tests```. 358 | 359 | You can run from the Automation tool in the Unreal Engine Editor, or via commandline: 360 | 361 | ```UnrealEditor-Cmd.exe 'path to the .uproject file' -NoSound -nullrhi -nosplash -log -Unattended -Nopause -TestExit="Automation Test Queue Empty" -ExecCmds="Automation RunTests MathVM"``` 362 | 363 | ## TODO 364 | 365 | * Investigate Templated version of FMathVM for supporting other types in addition to doubles (like a float one, an integer one or a vector based one) 366 | * Improve the Texture2D Resource 367 | * Investigate an error api for Resources 368 | * MetaSounds support (generate sounds from expressions) 369 | * Integration with Compushady (https://github.com/rdeioris/CompushadyUnreal) for GPU execution 370 | * Procedural mesh generation from expressions 371 | 372 | ## Builtin Functions in FMathVM 373 | 374 | ### abs(n) 375 | 376 | returns the absolute value of n. 377 | 378 | ### acos(n) 379 | 380 | returns the arccosine (inverse cosine) of n (in radians). 381 | 382 | ### all(...) 383 | 384 | returns 1 if all of the arguments are non 0. Otherwise returns 0. 385 | 386 | ### any(...) 387 | 388 | returns 1 if any of the arguments is non 0. Otherwise returns 0. 389 | 390 | ### asin(n) 391 | 392 | returns the arcsine of n. 393 | 394 | ### atan(n) 395 | 396 | returns teh arc tangent of n. 397 | 398 | ### ceil(n) 399 | 400 | returns the equal or next integer to n. Example if n equal 3 returns 3, if n equal 2.9 returns 3, if n equal 2.1 returns 3. 401 | 402 | ### clamp(n, x, y) 403 | 404 | returns n if n is between x and y, otherwise returns x if n is lower than n or y if n is greater than y. 405 | 406 | ### cos(n) 407 | 408 | returns the cosine of n. 409 | 410 | ### degrees(n) 411 | 412 | returns the value of n radians in degrees. 413 | 414 | ### distance(...) 415 | 416 | returns the distance between 2 vectors or scalars. Example for a tridimensional vector: distance(x1, y1, z1, x1, x2, z2). 417 | 418 | ### dot(...) 419 | 420 | returns the dot product between 2 vectors or scalars. Example for a tridimensional vector: dot(x1, y1, z1, x1, x2, z2). 421 | 422 | ### equal(...) 423 | 424 | returns 1 if all of the arguments contain the same value, otherwise 0. 425 | 426 | ### exp(n) 427 | 428 | return e raised to the power of n. 429 | 430 | ### exp2(n) 431 | 432 | return 2 raised to the power of n. 433 | 434 | ### floor(n) 435 | 436 | returns the equal or lesser integer to n. Example if n equal 3 returns 3, if n equal 2.9 returns 2, if n equal 2.1 returns 2. 437 | 438 | ### fract(n) 439 | 440 | returns the fractional part of n. 441 | 442 | ### gradient(n, ...) 443 | 444 | returns based on the specified map of gradients. Example with 3 ranges: gradient(n, range0, value0, range1, value1, range2, value2), gradient(n, 0, 1, 0.5, 100, 1, 1000), will returns 1 if n is equal 0, or 550 if n is equal 0.75. 445 | 446 | ### greater(n, m) 447 | 448 | returns 1 if n is greater than m. Otherwise 0. 449 | 450 | ### greater_equal(n, m) 451 | 452 | returns 1 if n is greater or equal than m. Otherwise 0. 453 | 454 | ### hue2b(n) 455 | 456 | returns the blue value given hue. 457 | 458 | ### hue2g(n) 459 | 460 | returns the green value given hue. 461 | 462 | ### hue2r(n) 463 | 464 | returns the red value given hue. 465 | 466 | ### length(...) 467 | 468 | returns the length of the specified vector. Example: length(1, 2, 3, 4) and length(1, 2) will returns respectively the length of a quadridimensional vector and a bidimensional one. 469 | 470 | ### lerp(x, y, n) 471 | 472 | returns linear interpolation of x to y with gradient n. 473 | 474 | ### less(n, m) 475 | 476 | returns 1 if n is less than m. Otherwise 0. 477 | 478 | ### less_equal(n, m) 479 | 480 | returns 1 if n is less or equal than m. Otherwise 0. 481 | 482 | ### log(n) 483 | 484 | returns the base E logarithm of n. 485 | 486 | ### log10(n) 487 | 488 | returns the base 10 logarithm of n. 489 | 490 | ### log2(n) 491 | 492 | returns the base 2 logarithm of n. 493 | 494 | ### logx(b, n) 495 | 496 | returns the base b logarithm of n. 497 | 498 | ### map(n, x0, x1, y0, y1) 499 | 500 | returns the value of n (comprised between x0 and y0) remapped to x1 and y1. Example: map(0.5, 0, 100, 1, 200) returns 150. 501 | 502 | ### max(...) 503 | 504 | returns the maximum value among the passed arguments. 505 | 506 | ### mean(...) 507 | 508 | returns the mean of the passed arguments. 509 | 510 | ### min(...) 511 | 512 | returns the minimum value among the passed arguments. 513 | 514 | ### mod(n, m) 515 | 516 | returns n modulo m (compatible with C fmod). 517 | 518 | ### not(n) 519 | 520 | returns 0 if n not equal to 0. 521 | 522 | ### pow(n, m) 523 | 524 | returns n to the power of m. 525 | 526 | ### radians(n) 527 | 528 | returns n degrees in radians. 529 | 530 | ### rand(n, m) 531 | 532 | returns a random value between n and m (inclusive). 533 | 534 | ### round(n) 535 | 536 | returns the nearest integer to n. Example 0.1 returns 0, 0.9 returns 1, 0.5 returns 0. 537 | 538 | ### round_even(n) 539 | 540 | returns the nearest even integer to n. Example 3.5 returns 4, 4.5 returns 4. 541 | 542 | ### sign(n) 543 | 544 | returns 1 if n > 0, -1 if n < 0, 0 if n is equal to 0. 545 | 546 | ### sin(n) 547 | 548 | returns the sine of n. 549 | 550 | ### sqrt(n) 551 | 552 | returns the square root of n. 553 | 554 | ### tan(n) 555 | 556 | returns the tangent of n. 557 | 558 | ### trunc(n) 559 | 560 | returns the integer part of n. 561 | 562 | ### read(id, ...) 563 | 564 | read from Resource id (check the Resources section) 565 | 566 | ### write(id, ...) 567 | 568 | write to Resource id (check the Resources section) 569 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdeioris/MathVM/ea409718b354458dfa0eed02c5a8ab749bdfabaa/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/MathVM/MathVM.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class MathVM : ModuleRules 6 | { 7 | public MathVM(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | // ... add other private include paths required here ... 21 | } 22 | ); 23 | 24 | 25 | PublicDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Core", 29 | // ... add other public dependencies that you statically link with here ... 30 | } 31 | ); 32 | 33 | 34 | PrivateDependencyModuleNames.AddRange( 35 | new string[] 36 | { 37 | "CoreUObject", 38 | "Engine", 39 | "SlateCore" 40 | // ... add private dependencies that you statically link with here ... 41 | } 42 | ); 43 | 44 | 45 | DynamicallyLoadedModuleNames.AddRange( 46 | new string[] 47 | { 48 | // ... add any modules that your module loads dynamically here ... 49 | } 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Source/MathVM/Private/MathVM.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | #include "MathVM.h" 4 | 5 | #define LOCTEXT_NAMESPACE "FMathVMModule" 6 | 7 | void FMathVMModule::StartupModule() 8 | { 9 | } 10 | 11 | void FMathVMModule::ShutdownModule() 12 | { 13 | } 14 | 15 | #undef LOCTEXT_NAMESPACE 16 | 17 | IMPLEMENT_MODULE(FMathVMModule, MathVM) -------------------------------------------------------------------------------- /Source/MathVM/Private/MathVMBlueprintFunctionLibrary.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | 4 | #include "MathVMBlueprintFunctionLibrary.h" 5 | #include "Async/Async.h" 6 | #include "Async/ParallelFor.h" 7 | #include "Runtime/Engine/Classes/Engine/Engine.h" 8 | #include "Engine/Canvas.h" 9 | 10 | 11 | bool MathVM::BlueprintUtility::RegisterResources(FMathVM& MathVM, const TArray& Resources, FString& Error) 12 | { 13 | for (int32 ResourceIndex = 0; ResourceIndex < Resources.Num(); ResourceIndex++) 14 | { 15 | UMathVMResourceObject* Resource = Resources[ResourceIndex]; 16 | if (!Resource) 17 | { 18 | Error = FString::Printf(TEXT("Null resource at index %d"), ResourceIndex); 19 | return false; 20 | } 21 | 22 | TSharedPtr NewResource = Resource->GetMathVMResource(); 23 | if (!NewResource) 24 | { 25 | Error = FString::Printf(TEXT("Null resource at index %d"), ResourceIndex); 26 | return false; 27 | } 28 | if (MathVM.RegisterResource(NewResource) < 0) 29 | { 30 | Error = FString::Printf(TEXT("Unable to register resource at index %d"), ResourceIndex); 31 | return false; 32 | } 33 | } 34 | 35 | return true; 36 | } 37 | 38 | UMathVMResourceObject* UMathVMBlueprintFunctionLibrary::MathVMResourceObjectFromTexture2D(UTexture2D* Texture) 39 | { 40 | if (!Texture) 41 | { 42 | return nullptr; 43 | } 44 | 45 | UMathVMResourceObject* NewResourceObject = NewObject(); 46 | 47 | NewResourceObject->SetMathVMResource(MakeShared(Texture)); 48 | 49 | return NewResourceObject; 50 | } 51 | 52 | UMathVMResourceObject* UMathVMBlueprintFunctionLibrary::MathVMResourceObjectFromCurveBase(UCurveBase* Curve) 53 | { 54 | if (!Curve) 55 | { 56 | return nullptr; 57 | } 58 | 59 | UMathVMResourceObject* NewResourceObject = NewObject(); 60 | 61 | NewResourceObject->SetMathVMResource(MakeShared(Curve)); 62 | 63 | return NewResourceObject; 64 | } 65 | 66 | UMathVMResourceObject* UMathVMBlueprintFunctionLibrary::MathVMResourceObjectAsDoubleArray(const int32 ArraySize) 67 | { 68 | if (ArraySize <= 0) 69 | { 70 | return nullptr; 71 | } 72 | 73 | UMathVMResourceObject* NewResourceObject = NewObject(); 74 | 75 | NewResourceObject->SetMathVMResource(MakeShared(ArraySize)); 76 | 77 | return NewResourceObject; 78 | } 79 | 80 | UMathVMResourceObject* UMathVMBlueprintFunctionLibrary::MathVMResourceObjectFromDataTable(UDataTable* DataTable, const TArray& FieldNames) 81 | { 82 | if (!DataTable || FieldNames.IsEmpty()) 83 | { 84 | return nullptr; 85 | } 86 | 87 | UMathVMResourceObject* NewResourceObject = NewObject(); 88 | 89 | NewResourceObject->SetMathVMResource(MakeShared(DataTable, FieldNames)); 90 | 91 | return NewResourceObject; 92 | } 93 | 94 | bool UMathVMBlueprintFunctionLibrary::MathVMRunSimple(const FString& Code, UPARAM(ref) TMap& LocalVariables, const TArray& Resources, double& Result, FString& Error) 95 | { 96 | if (Code.IsEmpty()) 97 | { 98 | Error = "Empty Code"; 99 | return false; 100 | } 101 | 102 | FMathVM MathVM; 103 | 104 | if (!MathVM::BlueprintUtility::RegisterResources(MathVM, Resources, Error)) 105 | { 106 | return false; 107 | } 108 | 109 | if (!MathVM.TokenizeAndCompile(Code)) 110 | { 111 | Error = MathVM.GetError(); 112 | return false; 113 | } 114 | 115 | return MathVM.ExecuteOne(LocalVariables, Result, Error); 116 | } 117 | 118 | bool UMathVMBlueprintFunctionLibrary::MathVMRunSimpleMulti(const FString& Code, UPARAM(ref) TMap& LocalVariables, const TArray& Resources, const int32 PopResults, TArray& Results, FString& Error) 119 | { 120 | if (Code.IsEmpty()) 121 | { 122 | Error = "Empty Code"; 123 | return false; 124 | } 125 | 126 | FMathVM MathVM; 127 | 128 | if (!MathVM::BlueprintUtility::RegisterResources(MathVM, Resources, Error)) 129 | { 130 | return false; 131 | } 132 | 133 | if (!MathVM.TokenizeAndCompile(Code)) 134 | { 135 | Error = MathVM.GetError(); 136 | return false; 137 | } 138 | 139 | return MathVM.Execute(LocalVariables, PopResults, Results, Error); 140 | } 141 | 142 | void UMathVMBlueprintFunctionLibrary::MathVMRun(const FString& Code, const TMap& GlobalVariables, const TMap& Constants, const TArray& Resources, const FMathVMEvaluatedWithResult& OnEvaluated, const int32 NumSamples, const FString& SampleLocalVariable) 143 | { 144 | if (Code.IsEmpty()) 145 | { 146 | OnEvaluated.ExecuteIfBound(FMathVMEvaluationResult("Empty Code")); 147 | return; 148 | } 149 | 150 | if (NumSamples < 1) 151 | { 152 | OnEvaluated.ExecuteIfBound(FMathVMEvaluationResult("Invalid NumThreads")); 153 | return; 154 | } 155 | 156 | if (SampleLocalVariable.IsEmpty()) 157 | { 158 | OnEvaluated.ExecuteIfBound(FMathVMEvaluationResult("ThreadIdLocalVariable Code")); 159 | return; 160 | } 161 | 162 | TSharedRef MathVM = MakeShared(); 163 | 164 | for (const TPair& Pair : Constants) 165 | { 166 | if (!MathVM->RegisterConst(Pair.Key, Pair.Value)) 167 | { 168 | OnEvaluated.ExecuteIfBound(FMathVMEvaluationResult(FString::Printf(TEXT("Unable to register const \"%s\""), *Pair.Key))); 169 | return; 170 | } 171 | } 172 | 173 | for (const TPair& Pair : GlobalVariables) 174 | { 175 | if (!MathVM->RegisterGlobalVariable(Pair.Key, Pair.Value)) 176 | { 177 | OnEvaluated.ExecuteIfBound(FMathVMEvaluationResult(FString::Printf(TEXT("Unable to register global variable \"%s\""), *Pair.Key))); 178 | return; 179 | } 180 | } 181 | 182 | FString RegisterResourcesError; 183 | if (!MathVM::BlueprintUtility::RegisterResources(*MathVM, Resources, RegisterResourcesError)) 184 | { 185 | OnEvaluated.ExecuteIfBound(FMathVMEvaluationResult(RegisterResourcesError)); 186 | return; 187 | } 188 | 189 | if (!MathVM->TokenizeAndCompile(Code)) 190 | { 191 | OnEvaluated.ExecuteIfBound(FMathVMEvaluationResult(MathVM->GetError())); 192 | return; 193 | } 194 | 195 | Async(EAsyncExecution::Thread, [MathVM, NumSamples, GlobalVariables, SampleLocalVariable, OnEvaluated]() 196 | { 197 | ParallelFor(NumSamples, [&](const int32 ThreadId) 198 | { 199 | TMap LocalVariables; 200 | LocalVariables.Add(SampleLocalVariable, ThreadId); 201 | MathVM->ExecuteStealth(LocalVariables); 202 | }); 203 | 204 | FGraphEventRef Task = FFunctionGraphTask::CreateAndDispatchWhenReady([&]() 205 | { 206 | OnEvaluated.ExecuteIfBound(FMathVMEvaluationResult(MathVM->GetGlobalVariables())); 207 | }, TStatId(), nullptr, ENamedThreads::GameThread); 208 | FTaskGraphInterface::Get().WaitUntilTaskCompletes(Task); 209 | }); 210 | } 211 | 212 | void UMathVMBlueprintFunctionLibrary::MathVMPlotter(UObject* WorldContextObject, const FString& Code, const int32 NumSamples, const TMap& VariablesToPlot, const TArray& TextsToPlot, const TMap& Constants, const TMap& GlobalVariables, const TArray& Resources, const FMathVMPlotGenerated& OnPlotGenerated, const FMathVMPlotterConfig& PlotterConfig, const double DomainMin, const double DomainMax, const FString& SampleLocalVariable) 213 | { 214 | if (Code.IsEmpty()) 215 | { 216 | OnPlotGenerated.ExecuteIfBound(nullptr, FMathVMEvaluationResult("Empty Code")); 217 | return; 218 | } 219 | 220 | if (NumSamples < 1) 221 | { 222 | OnPlotGenerated.ExecuteIfBound(nullptr, FMathVMEvaluationResult("Invalid number of samples")); 223 | return; 224 | } 225 | 226 | if (VariablesToPlot.IsEmpty()) 227 | { 228 | OnPlotGenerated.ExecuteIfBound(nullptr, FMathVMEvaluationResult("Empty VariablesToPlot")); 229 | return; 230 | } 231 | 232 | if (SampleLocalVariable.IsEmpty()) 233 | { 234 | OnPlotGenerated.ExecuteIfBound(nullptr, FMathVMEvaluationResult("Empty Code")); 235 | } 236 | 237 | if (!PlotterConfig.RenderTarget && (PlotterConfig.TextureWidth < 1 || PlotterConfig.TextureHeight < 1)) 238 | { 239 | OnPlotGenerated.ExecuteIfBound(nullptr, FMathVMEvaluationResult("Invalid texture size")); 240 | return; 241 | } 242 | 243 | FMathVM MathVM; 244 | 245 | for (const TPair& Const : Constants) 246 | { 247 | if (!MathVM.RegisterConst(Const.Key, Const.Value)) 248 | { 249 | OnPlotGenerated.ExecuteIfBound(nullptr, FMathVMEvaluationResult("Invalid constants")); 250 | return; 251 | } 252 | } 253 | 254 | for (const TPair& GlobalVariable : GlobalVariables) 255 | { 256 | if (!MathVM.RegisterGlobalVariable(GlobalVariable.Key, GlobalVariable.Value)) 257 | { 258 | OnPlotGenerated.ExecuteIfBound(nullptr, FMathVMEvaluationResult("Invalid global variables")); 259 | return; 260 | } 261 | } 262 | 263 | FString RegisterResourcesError; 264 | if (!MathVM::BlueprintUtility::RegisterResources(MathVM, Resources, RegisterResourcesError)) 265 | { 266 | OnPlotGenerated.ExecuteIfBound(nullptr, RegisterResourcesError); 267 | return; 268 | } 269 | 270 | UTextureRenderTarget2D* RenderTarget = PlotterConfig.RenderTarget; 271 | 272 | if (!RenderTarget) 273 | { 274 | RenderTarget = NewObject(); 275 | 276 | if (!RenderTarget) 277 | { 278 | OnPlotGenerated.ExecuteIfBound(nullptr, FMathVMEvaluationResult("Unable to create CanvasRenderTarget2D")); 279 | return; 280 | } 281 | 282 | RenderTarget->InitAutoFormat(PlotterConfig.TextureWidth, PlotterConfig.TextureHeight); 283 | } 284 | 285 | const int32 TextureWidth = RenderTarget->SizeX; 286 | const int32 TextureHeight = RenderTarget->SizeY; 287 | 288 | if (!MathVM.TokenizeAndCompile(Code)) 289 | { 290 | OnPlotGenerated.ExecuteIfBound(nullptr, FMathVMEvaluationResult(MathVM.GetError())); 291 | return; 292 | } 293 | 294 | TMap> Points; 295 | for (const TPair& Pair : VariablesToPlot) 296 | { 297 | TArray& PointsCoordinates = Points.Add(Pair.Key); 298 | // it is important to set it to invalid coordinates to avoid artifacts 299 | PointsCoordinates.AddUninitialized(NumSamples); 300 | for (int32 CoordIndex = 0; CoordIndex < NumSamples; CoordIndex++) 301 | { 302 | PointsCoordinates[CoordIndex] = FVector2D(-1, -1); 303 | } 304 | } 305 | 306 | FString ErrorZero; 307 | 308 | ParallelFor(NumSamples, [&](const int32 SampleIndex) 309 | { 310 | const double X = FMath::GetMappedRangeValueUnclamped(FVector2D(0, NumSamples - 1), FVector2D(PlotterConfig.BorderSize.Left + PlotterConfig.BorderThickness, TextureWidth - 1 - PlotterConfig.BorderSize.Right - PlotterConfig.BorderThickness), SampleIndex); 311 | 312 | TMap LocalVariables; 313 | LocalVariables.Add(SampleLocalVariable, SampleIndex); 314 | 315 | bool bSuccess = false; 316 | if (SampleIndex == 0) 317 | { 318 | bSuccess = MathVM.ExecuteAndDiscard(LocalVariables, ErrorZero); 319 | } 320 | else 321 | { 322 | bSuccess = MathVM.ExecuteStealth(LocalVariables); 323 | } 324 | 325 | if (bSuccess) 326 | { 327 | for (const TPair& Pair : VariablesToPlot) 328 | { 329 | if (LocalVariables.Contains(Pair.Key)) 330 | { 331 | const double Y = FMath::GetMappedRangeValueUnclamped(FVector2D(DomainMin, DomainMax), FVector2D(PlotterConfig.BorderSize.Bottom + PlotterConfig.BorderThickness, TextureHeight - 1 - PlotterConfig.BorderSize.Top - PlotterConfig.BorderThickness), FMath::Clamp(LocalVariables[Pair.Key], DomainMin, DomainMax)); 332 | Points[Pair.Key][SampleIndex] = FVector2D(X, (TextureHeight - 1 - PlotterConfig.BorderSize.Top - PlotterConfig.BorderThickness) - Y + PlotterConfig.BorderSize.Bottom + PlotterConfig.BorderThickness); 333 | } 334 | } 335 | } 336 | }); 337 | 338 | if (!ErrorZero.IsEmpty()) 339 | { 340 | OnPlotGenerated.ExecuteIfBound(nullptr, FMathVMEvaluationResult(ErrorZero)); 341 | return; 342 | } 343 | 344 | FMathVMEvaluationResult Result(MathVM.GetGlobalVariables()); 345 | UCanvas* Canvas = nullptr; 346 | FVector2D CanvasSize; 347 | FDrawToRenderTargetContext DrawContext; 348 | UKismetRenderingLibrary::BeginDrawCanvasToRenderTarget(WorldContextObject, RenderTarget, Canvas, CanvasSize, DrawContext); 349 | 350 | if (Canvas) 351 | { 352 | UKismetRenderingLibrary::ClearRenderTarget2D(WorldContextObject, RenderTarget, PlotterConfig.BackgroundColor); 353 | 354 | Canvas->K2_DrawLine(PlotterConfig.BorderSize.GetTopLeft(), FVector2D(TextureWidth - PlotterConfig.BorderSize.Right, PlotterConfig.BorderSize.Top), PlotterConfig.BorderThickness, PlotterConfig.BorderColor); 355 | Canvas->K2_DrawLine(PlotterConfig.BorderSize.GetTopLeft(), FVector2D(PlotterConfig.BorderSize.Left, TextureHeight - PlotterConfig.BorderSize.Bottom), PlotterConfig.BorderThickness, PlotterConfig.BorderColor); 356 | Canvas->K2_DrawLine(FVector2D(TextureWidth - PlotterConfig.BorderSize.Right, PlotterConfig.BorderSize.Top), FVector2D(TextureWidth - PlotterConfig.BorderSize.Right, TextureHeight - PlotterConfig.BorderSize.Bottom), PlotterConfig.BorderThickness, PlotterConfig.BorderColor); 357 | Canvas->K2_DrawLine(FVector2D(PlotterConfig.BorderSize.Left, TextureHeight - PlotterConfig.BorderSize.Bottom), FVector2D(TextureWidth - PlotterConfig.BorderSize.Right, TextureHeight - PlotterConfig.BorderSize.Bottom), PlotterConfig.BorderThickness, PlotterConfig.BorderColor); 358 | 359 | if (PlotterConfig.GridVerticalScale > 0) 360 | { 361 | const double Step = ((TextureWidth - PlotterConfig.BorderSize.Left - PlotterConfig.BorderSize.Right - (PlotterConfig.BorderThickness * 2)) * PlotterConfig.GridVerticalScale) / (NumSamples - 1); 362 | const int32 NumScaledSamples = (NumSamples - 1) / PlotterConfig.GridVerticalScale; 363 | 364 | for (int32 SampleIndex = 0; SampleIndex < NumScaledSamples + 1; SampleIndex++) 365 | { 366 | Canvas->K2_DrawLine( 367 | FVector2D(PlotterConfig.BorderSize.Left + PlotterConfig.BorderThickness + (Step * SampleIndex), TextureHeight - PlotterConfig.BorderSize.Bottom - PlotterConfig.BorderThickness), 368 | FVector2D(PlotterConfig.BorderSize.Left + PlotterConfig.BorderThickness + (Step * SampleIndex), PlotterConfig.BorderSize.Top + PlotterConfig.BorderThickness), 369 | PlotterConfig.GridThickness, PlotterConfig.GridColor); 370 | } 371 | } 372 | 373 | for (const TPair>& Pair : Points) 374 | { 375 | const FMathVMPlot& Plot = VariablesToPlot[Pair.Key]; 376 | 377 | if (Plot.Shape == EMathVMPlotterShape::Line || Plot.Shape == EMathVMPlotterShape::LineAndPoint) 378 | { 379 | for (int32 PointIndex = 0; PointIndex < Pair.Value.Num() - 1; PointIndex++) 380 | { 381 | const FVector2D& Point0 = Pair.Value[PointIndex]; 382 | const FVector2D& Point1 = Pair.Value[PointIndex + 1]; 383 | if (Point0.X < 0 || Point0.Y < 0 || Point1.X < 0 || Point1.Y < 0) 384 | { 385 | continue; 386 | } 387 | Canvas->K2_DrawLine(Point0, Point1, Plot.Thickness, Plot.Color); 388 | if (Plot.Shape == EMathVMPlotterShape::LineAndPoint) 389 | { 390 | Canvas->K2_DrawBox(Point0 - FVector2D(Plot.Thickness, Plot.Thickness), FVector2D(Plot.Thickness, Plot.Thickness) * 2, Plot.Thickness * 2, Plot.Color2); 391 | } 392 | } 393 | 394 | if (Plot.Shape == EMathVMPlotterShape::LineAndPoint) 395 | { 396 | const FVector2D& Point0 = Pair.Value[Pair.Value.Num() - 1]; 397 | Canvas->K2_DrawBox(Point0 - FVector2D(Plot.Thickness, Plot.Thickness), FVector2D(Plot.Thickness, Plot.Thickness) * 2, Plot.Thickness * 2, Plot.Color2); 398 | } 399 | } 400 | else if (Plot.Shape == EMathVMPlotterShape::Box) 401 | { 402 | for (int32 PointIndex = 0; PointIndex < Pair.Value.Num(); PointIndex++) 403 | { 404 | const FVector2D& Point = Pair.Value[PointIndex]; 405 | if (Point.X < 0 || Point.Y < 0) 406 | { 407 | continue; 408 | } 409 | Canvas->K2_DrawBox(Point - FVector2D(Plot.Thickness, Plot.Thickness) * 0.5, FVector2D(Plot.Thickness, Plot.Thickness), Plot.Thickness, Plot.Color); 410 | } 411 | } 412 | } 413 | 414 | for (const FMathVMText& Text : TextsToPlot) 415 | { 416 | UFont* Font = Text.Font; 417 | if (!Font) 418 | { 419 | Font = GEngine->GetTinyFont(); 420 | } 421 | 422 | FString TextContent = Text.Text; 423 | for (const TPair& GlobalVariable : Result.GlobalVariables) 424 | { 425 | TextContent = TextContent.Replace(*FString("{" + GlobalVariable.Key + "}"), *FString::SanitizeFloat(GlobalVariable.Value)); 426 | } 427 | 428 | Canvas->K2_DrawText(Font, TextContent, Text.Position * FVector2D(TextureWidth, TextureHeight), Text.Scaling, Text.Color); 429 | } 430 | 431 | } 432 | 433 | UKismetRenderingLibrary::EndDrawCanvasToRenderTarget(WorldContextObject, DrawContext); 434 | 435 | OnPlotGenerated.ExecuteIfBound(RenderTarget, Result); 436 | } -------------------------------------------------------------------------------- /Source/MathVM/Private/MathVMBuiltinFunctions.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024-2025, Roberto De Ioris. 2 | 3 | #include "MathVMBuiltinFunctions.h" 4 | #include "Math/RandomStream.h" 5 | 6 | namespace MathVM 7 | { 8 | namespace BuiltinFunctions 9 | { 10 | bool Abs(MATHVM_ARGS) 11 | { 12 | MATHVM_RETURN(FMath::Abs(Args[0])); 13 | } 14 | 15 | bool ACos(MATHVM_ARGS) 16 | { 17 | MATHVM_RETURN(FMath::Acos(Args[0])); 18 | } 19 | 20 | bool All(MATHVM_ARGS) 21 | { 22 | for (const double Value : Args) 23 | { 24 | if (Value == 0.0) 25 | { 26 | MATHVM_RETURN(0); 27 | } 28 | } 29 | MATHVM_RETURN(1); 30 | } 31 | 32 | bool Any(MATHVM_ARGS) 33 | { 34 | for (const double Value : Args) 35 | { 36 | if (Value != 0.0) 37 | { 38 | MATHVM_RETURN(1); 39 | } 40 | } 41 | MATHVM_RETURN(0); 42 | } 43 | 44 | bool ASin(MATHVM_ARGS) 45 | { 46 | MATHVM_RETURN(FMath::Asin(Args[0])); 47 | } 48 | 49 | bool ATan(MATHVM_ARGS) 50 | { 51 | MATHVM_RETURN(FMath::Atan(Args[0])); 52 | } 53 | 54 | bool Ceil(MATHVM_ARGS) 55 | { 56 | MATHVM_RETURN(FMath::CeilToDouble(Args[0])); 57 | } 58 | 59 | bool Clamp(MATHVM_ARGS) 60 | { 61 | MATHVM_RETURN(FMath::Clamp(Args[0], Args[1], Args[2])); 62 | } 63 | 64 | bool Cos(MATHVM_ARGS) 65 | { 66 | MATHVM_RETURN(FMath::Cos(Args[0])); 67 | } 68 | 69 | bool Degrees(MATHVM_ARGS) 70 | { 71 | MATHVM_RETURN(FMath::RadiansToDegrees(Args[0])); 72 | } 73 | 74 | bool Distance(MATHVM_ARGS) 75 | { 76 | if (Args.Num() < 2) 77 | { 78 | MATHVM_ERROR("distance expects at least 2 arguments"); 79 | } 80 | 81 | if (Args.Num() % 2 != 0) 82 | { 83 | MATHVM_ERROR("distance number of arguments must be even"); 84 | } 85 | 86 | const int32 NumElements = Args.Num() / 2; 87 | 88 | TArray Values; 89 | Values.AddUninitialized(NumElements); 90 | 91 | for (int32 ElementIndex = 0; ElementIndex < NumElements; ElementIndex++) 92 | { 93 | Values[ElementIndex] = Args[NumElements + ElementIndex] - Args[ElementIndex]; 94 | } 95 | 96 | double TotalLength = 0; 97 | 98 | for (int32 ElementIndex = 0; ElementIndex < NumElements; ElementIndex++) 99 | { 100 | TotalLength += Values[ElementIndex] * Values[ElementIndex]; 101 | } 102 | 103 | MATHVM_RETURN(FMath::Sqrt(TotalLength)); 104 | } 105 | 106 | bool Dot(MATHVM_ARGS) 107 | { 108 | if (Args.Num() < 2) 109 | { 110 | MATHVM_ERROR("dot expects at least 2 arguments"); 111 | } 112 | 113 | if (Args.Num() % 2 != 0) 114 | { 115 | MATHVM_ERROR("dot number of arguments must be even"); 116 | } 117 | 118 | const int32 NumElements = Args.Num() / 2; 119 | 120 | float Total = 0; 121 | 122 | for (int32 ElementIndex = 0; ElementIndex < NumElements; ElementIndex++) 123 | { 124 | Total += Args[ElementIndex] * Args[NumElements + ElementIndex]; 125 | } 126 | 127 | MATHVM_RETURN(Total); 128 | } 129 | 130 | bool Equal(MATHVM_ARGS) 131 | { 132 | if (Args.Num() < 2) 133 | { 134 | MATHVM_ERROR("equal expects at least 2 arguments"); 135 | } 136 | 137 | for (int32 ArgIndex = 1; ArgIndex < Args.Num(); ArgIndex++) 138 | { 139 | if (Args[ArgIndex] != Args[0]) 140 | { 141 | MATHVM_RETURN(0); 142 | } 143 | } 144 | 145 | MATHVM_RETURN(1); 146 | } 147 | 148 | bool Exp(MATHVM_ARGS) 149 | { 150 | MATHVM_RETURN(FMath::Exp(Args[0])); 151 | } 152 | 153 | bool Exp2(MATHVM_ARGS) 154 | { 155 | MATHVM_RETURN(FMath::Exp2(Args[0])); 156 | } 157 | 158 | bool Gradient(MATHVM_ARGS) 159 | { 160 | if (Args.Num() < 5) 161 | { 162 | MATHVM_ERROR("gradient expects at least 5 arguments"); 163 | } 164 | 165 | if ((Args.Num() - 1) % 2 != 0) 166 | { 167 | return false; 168 | } 169 | 170 | if (Args[3] < Args[1]) 171 | { 172 | return false; 173 | } 174 | 175 | const double Value = Args[0]; 176 | TArray> Ranges; 177 | 178 | Ranges.Add({ Args[1], Args[2] }); 179 | Ranges.Add({ Args[3], Args[4] }); 180 | 181 | for (int32 ArgIndex = 5; ArgIndex < Args.Num(); ArgIndex += 2) 182 | { 183 | if (Args[ArgIndex] < Args[ArgIndex - 2]) 184 | { 185 | return false; 186 | } 187 | Ranges.Add({ Args[ArgIndex], Args[ArgIndex + 1] }); 188 | } 189 | 190 | int32 FoundRangeIndex = -1; 191 | 192 | for (int32 RangeIndex = 0; RangeIndex < Ranges.Num(); RangeIndex++) 193 | { 194 | if (Ranges[RangeIndex].Key >= Value) 195 | { 196 | if (RangeIndex == 0) 197 | { 198 | FoundRangeIndex = 0; 199 | } 200 | else 201 | { 202 | FoundRangeIndex = RangeIndex - 1; 203 | } 204 | break; 205 | } 206 | } 207 | 208 | if (FoundRangeIndex < 0 || FoundRangeIndex >= Ranges.Num()) 209 | { 210 | MATHVM_RETURN(Ranges.Last().Value); 211 | } 212 | 213 | const double Delta = Value - Ranges[FoundRangeIndex].Key; 214 | const double Fraction = Delta / (Ranges[FoundRangeIndex + 1].Key - Ranges[FoundRangeIndex].Key); 215 | 216 | MATHVM_RETURN(FMath::Lerp(Ranges[FoundRangeIndex].Value, Ranges[FoundRangeIndex + 1].Value, Fraction)); 217 | } 218 | 219 | bool Floor(MATHVM_ARGS) 220 | { 221 | MATHVM_RETURN(FMath::Floor(Args[0])); 222 | } 223 | 224 | bool Fract(MATHVM_ARGS) 225 | { 226 | MATHVM_RETURN(FMath::Fractional(Args[0])); 227 | } 228 | 229 | bool Greater(MATHVM_ARGS) 230 | { 231 | MATHVM_RETURN(Args[0] > Args[1] ? 1 : 0); 232 | } 233 | 234 | bool GreaterEqual(MATHVM_ARGS) 235 | { 236 | MATHVM_RETURN(Args[0] >= Args[1] ? 1 : 0); 237 | } 238 | 239 | bool Hue2B(MATHVM_ARGS) 240 | { 241 | MATHVM_RETURN(FMath::Clamp(2 - FMath::Abs(Args[0] * 6 - 4), 0, 1)); 242 | } 243 | 244 | bool Hue2G(MATHVM_ARGS) 245 | { 246 | MATHVM_RETURN(FMath::Clamp(2 - FMath::Abs(Args[0] * 6 - 2), 0, 1)); 247 | } 248 | 249 | bool Hue2R(MATHVM_ARGS) 250 | { 251 | MATHVM_RETURN(FMath::Clamp(FMath::Abs(Args[0] * 6 - 3) - 1, 0, 1)); 252 | } 253 | 254 | bool Length(MATHVM_ARGS) 255 | { 256 | double TotalLength = 0; 257 | for (const double Arg : Args) 258 | { 259 | TotalLength += Arg * Arg; 260 | } 261 | 262 | MATHVM_RETURN(FMath::Sqrt(TotalLength)); 263 | } 264 | 265 | bool Lerp(MATHVM_ARGS) 266 | { 267 | MATHVM_RETURN(FMath::Lerp(Args[0], Args[1], Args[2])); 268 | } 269 | 270 | bool Less(MATHVM_ARGS) 271 | { 272 | MATHVM_RETURN(Args[0] < Args[1] ? 1 : 0); 273 | } 274 | 275 | bool LessEqual(MATHVM_ARGS) 276 | { 277 | MATHVM_RETURN(Args[0] <= Args[1] ? 1 : 0); 278 | } 279 | 280 | bool Log(MATHVM_ARGS) 281 | { 282 | MATHVM_RETURN(FMath::Loge(Args[0])); 283 | } 284 | 285 | bool Log10(MATHVM_ARGS) 286 | { 287 | MATHVM_RETURN(FMath::LogX(10, Args[0])); 288 | } 289 | 290 | bool Log2(MATHVM_ARGS) 291 | { 292 | MATHVM_RETURN(FMath::Log2(Args[0])); 293 | } 294 | 295 | bool LogX(MATHVM_ARGS) 296 | { 297 | MATHVM_RETURN(FMath::LogX(Args[0], Args[1])); 298 | } 299 | 300 | bool Map(MATHVM_ARGS) 301 | { 302 | MATHVM_RETURN(FMath::GetMappedRangeValueUnclamped(FVector2D(Args[1], Args[3]), FVector2D(Args[2], Args[4]), Args[0])); 303 | } 304 | 305 | bool Max(MATHVM_ARGS) 306 | { 307 | if (Args.Num() < 2) 308 | { 309 | MATHVM_ERROR("max expects at least 2 arguments"); 310 | }; 311 | 312 | double MaxValue = FMath::Max(Args[0], Args[1]); 313 | for (int32 ArgIndex = 2; ArgIndex < Args.Num(); ArgIndex++) 314 | { 315 | MaxValue = FMath::Max(MaxValue, Args[ArgIndex]); 316 | } 317 | 318 | MATHVM_RETURN(MaxValue); 319 | } 320 | 321 | bool Mean(MATHVM_ARGS) 322 | { 323 | if (Args.Num() < 2) 324 | { 325 | MATHVM_ERROR("mean expects at least 2 arguments"); 326 | }; 327 | 328 | double Result = 0; 329 | for (const double Value : Args) 330 | { 331 | Result += Value; 332 | } 333 | 334 | MATHVM_RETURN(Result / Args.Num()); 335 | } 336 | 337 | bool Min(MATHVM_ARGS) 338 | { 339 | if (Args.Num() < 2) 340 | { 341 | MATHVM_ERROR("min expects at least 2 arguments"); 342 | }; 343 | 344 | double MinValue = FMath::Min(Args[0], Args[1]); 345 | for (int32 ArgIndex = 2; ArgIndex < Args.Num(); ArgIndex++) 346 | { 347 | MinValue = FMath::Min(MinValue, Args[ArgIndex]); 348 | } 349 | 350 | MATHVM_RETURN(MinValue); 351 | } 352 | 353 | bool Mod(MATHVM_ARGS) 354 | { 355 | MATHVM_RETURN(FMath::Fmod(Args[0], Args[1])); 356 | } 357 | 358 | bool Not(MATHVM_ARGS) 359 | { 360 | MATHVM_RETURN(Args[0] == 0.0 ? 1 : 0); 361 | } 362 | 363 | bool Pow(MATHVM_ARGS) 364 | { 365 | MATHVM_RETURN(FMath::Pow(Args[0], Args[1])); 366 | } 367 | 368 | bool Radians(MATHVM_ARGS) 369 | { 370 | MATHVM_RETURN(FMath::DegreesToRadians(Args[0])); 371 | } 372 | 373 | bool Rand(MATHVM_ARGS) 374 | { 375 | static FRandomStream RandomStream; 376 | MATHVM_RETURN(RandomStream.FRandRange(Args[0], Args[1])); 377 | } 378 | 379 | bool Round(MATHVM_ARGS) 380 | { 381 | MATHVM_RETURN(FMath::RoundToDouble(Args[0])); 382 | } 383 | 384 | bool RoundEven(MATHVM_ARGS) 385 | { 386 | MATHVM_RETURN(FMath::RoundHalfToEven(Args[0])); 387 | } 388 | 389 | bool Sign(MATHVM_ARGS) 390 | { 391 | MATHVM_RETURN(FMath::Sign(Args[0])); 392 | } 393 | 394 | bool Sin(MATHVM_ARGS) 395 | { 396 | MATHVM_RETURN(FMath::Sin(Args[0])); 397 | } 398 | 399 | bool Sqrt(MATHVM_ARGS) 400 | { 401 | MATHVM_RETURN(FMath::Sqrt(Args[0])); 402 | } 403 | 404 | bool Tan(MATHVM_ARGS) 405 | { 406 | MATHVM_RETURN(FMath::Tan(Args[0])); 407 | } 408 | 409 | bool Trunc(MATHVM_ARGS) 410 | { 411 | MATHVM_RETURN(FMath::TruncToDouble(Args[0])); 412 | } 413 | 414 | // Resources 415 | 416 | bool Read(MATHVM_ARGS) 417 | { 418 | if (Args.Num() < 1) 419 | { 420 | MATHVM_ERROR("read expects at least 1 argument"); 421 | }; 422 | 423 | TArray InterfaceArgs; 424 | for (int32 Index = 1; Index < Args.Num(); Index++) 425 | { 426 | InterfaceArgs.Add(Args[Index]); 427 | } 428 | 429 | MATHVM_RETURN(CallContext.ReadResource(static_cast(Args[0]), InterfaceArgs)); 430 | } 431 | 432 | bool Write(MATHVM_ARGS) 433 | { 434 | if (Args.Num() < 1) 435 | { 436 | MATHVM_ERROR("write expects at least 1 argument"); 437 | }; 438 | 439 | TArray InterfaceArgs; 440 | for (int32 Index = 1; Index < Args.Num(); Index++) 441 | { 442 | InterfaceArgs.Add(Args[Index]); 443 | } 444 | 445 | CallContext.WriteResource(static_cast(Args[0]), InterfaceArgs); 446 | return true; 447 | } 448 | } 449 | } -------------------------------------------------------------------------------- /Source/MathVM/Private/MathVMClasses.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | #include "MathVMBuiltinFunctions.h" 4 | 5 | FMathVMBase::FMathVMBase() 6 | { 7 | OperatorAdd = [](FMathVMCallContext& CallContext) -> bool 8 | { 9 | double A = 0, B = 0; 10 | if (!CallContext.PopArgument(B)) 11 | { 12 | return false; 13 | } 14 | if (!CallContext.PopArgument(A)) 15 | { 16 | return false; 17 | } 18 | 19 | return CallContext.PushResult(A + B); 20 | }; 21 | 22 | OperatorSub = [](FMathVMCallContext& CallContext) -> bool 23 | { 24 | double A = 0, B = 0; 25 | if (!CallContext.PopArgument(B)) 26 | { 27 | return false; 28 | } 29 | if (!CallContext.PopArgument(A)) 30 | { 31 | return false; 32 | } 33 | 34 | return CallContext.PushResult(A - B); 35 | }; 36 | 37 | OperatorMul = [](FMathVMCallContext& CallContext) -> bool 38 | { 39 | double A = 0, B = 0; 40 | if (!CallContext.PopArgument(B)) 41 | { 42 | return false; 43 | } 44 | if (!CallContext.PopArgument(A)) 45 | { 46 | return false; 47 | } 48 | 49 | return CallContext.PushResult(A * B); 50 | }; 51 | 52 | OperatorDiv = [](FMathVMCallContext& CallContext) -> bool 53 | { 54 | double A = 0, B = 0; 55 | if (!CallContext.PopArgument(B)) 56 | { 57 | return false; 58 | } 59 | 60 | if (B == 0.0) 61 | { 62 | return CallContext.SetError("Division by zero"); 63 | } 64 | 65 | if (!CallContext.PopArgument(A)) 66 | { 67 | return false; 68 | } 69 | 70 | return CallContext.PushResult(A / B); 71 | }; 72 | 73 | OperatorMod = [](FMathVMCallContext& CallContext) -> bool 74 | { 75 | double A = 0, B = 0; 76 | if (!CallContext.PopArgument(B)) 77 | { 78 | return false; 79 | } 80 | if (!CallContext.PopArgument(A)) 81 | { 82 | return false; 83 | } 84 | 85 | return CallContext.PushResult(static_cast(A) % static_cast(B)); 86 | }; 87 | 88 | OperatorAssign = [](FMathVMCallContext& CallContext) -> bool 89 | { 90 | FString A; 91 | double B = 0; 92 | 93 | if (!CallContext.PopArgument(B)) 94 | { 95 | return false; 96 | } 97 | if (!CallContext.PopName(A)) 98 | { 99 | return false; 100 | } 101 | 102 | if (CallContext.LocalVariables.Contains(A)) 103 | { 104 | CallContext.LocalVariables[A] = B; 105 | } 106 | else if (CallContext.MathVM.HasGlobalVariable(A)) 107 | { 108 | CallContext.MathVM.SetGlobalVariable(A, B); 109 | } 110 | else 111 | { 112 | CallContext.LocalVariables.Add(A, B); 113 | } 114 | 115 | return true; 116 | }; 117 | } 118 | 119 | const FString& FMathVMBase::GetError() const 120 | { 121 | return LastError; 122 | } 123 | 124 | bool FMathVMBase::SetError(const FString& InError) 125 | { 126 | LastError = InError; 127 | return false; 128 | } 129 | 130 | bool FMathVMBase::RegisterFunction(const FString& Name, FMathVMFunction Callable, const int32 NumArgs) 131 | { 132 | if (!MathVM::Utils::SanitizeName(Name)) 133 | { 134 | return false; 135 | } 136 | 137 | if (Functions.Contains(Name)) 138 | { 139 | Functions[Name] = { Callable, NumArgs }; 140 | } 141 | else 142 | { 143 | Functions.Add(Name, { Callable, NumArgs }); 144 | } 145 | 146 | return true; 147 | } 148 | 149 | bool FMathVMBase::HasGlobalVariable(const FString& Name) const 150 | { 151 | return GlobalVariables.Contains(Name); 152 | } 153 | 154 | bool FMathVMBase::HasConst(const FString& Name) const 155 | { 156 | return Constants.Contains(Name); 157 | } 158 | 159 | void FMathVMBase::SetGlobalVariable(const FString& Name, const double Value) 160 | { 161 | GlobalVariables[Name] = Value; 162 | } 163 | 164 | double FMathVMBase::GetGlobalVariable(const FString& Name) const 165 | { 166 | return GlobalVariables[Name]; 167 | } 168 | 169 | const TMap& FMathVMBase::GetGlobalVariables() const 170 | { 171 | return GlobalVariables; 172 | } 173 | 174 | double FMathVMBase::GetConst(const FString& Name) 175 | { 176 | return Constants[Name]; 177 | } 178 | 179 | bool MathVM::Utils::SanitizeName(const FString& Name) 180 | { 181 | if (Name.IsEmpty()) 182 | { 183 | return false; 184 | } 185 | 186 | TCHAR FirstChar = Name[0]; 187 | const bool bValidStart = (FirstChar >= 'A' && FirstChar <= 'Z') || (FirstChar >= 'a' && FirstChar <= 'z') || FirstChar == '_'; 188 | if (!bValidStart) 189 | { 190 | return false; 191 | } 192 | 193 | for (int32 CharIndex = 1; CharIndex < Name.Len(); CharIndex++) 194 | { 195 | const TCHAR Char = Name[CharIndex]; 196 | const bool bValidChar = (Char >= 'A' && Char <= 'Z') || (Char >= 'a' && Char <= 'z') || (Char >= '0' && Char <= '9') || Char == '_'; 197 | if (!bValidChar) 198 | { 199 | return false; 200 | } 201 | } 202 | 203 | return true; 204 | } 205 | 206 | bool FMathVMBase::RegisterConst(const FString& Name, const double Value) 207 | { 208 | if (!MathVM::Utils::SanitizeName(Name)) 209 | { 210 | return false; 211 | } 212 | 213 | if (Constants.Contains(Name)) 214 | { 215 | return false; 216 | } 217 | 218 | Constants.Add(Name, Value); 219 | 220 | return true; 221 | } 222 | 223 | bool FMathVMBase::RegisterGlobalVariable(const FString& Name, const double Value) 224 | { 225 | if (!MathVM::Utils::SanitizeName(Name)) 226 | { 227 | return false; 228 | } 229 | 230 | if (GlobalVariables.Contains(Name)) 231 | { 232 | GlobalVariables[Name] = Value; 233 | } 234 | else 235 | { 236 | GlobalVariables.Add(Name, Value); 237 | } 238 | 239 | return true; 240 | } 241 | 242 | int32 FMathVMBase::RegisterResource(TSharedPtr Resource) 243 | { 244 | return Resources.Add(Resource); 245 | } 246 | 247 | TSharedPtr FMathVMBase::GetResource(const int32 Index) const 248 | { 249 | if (Resources.IsValidIndex(Index)) 250 | { 251 | return Resources[Index]; 252 | } 253 | 254 | return nullptr; 255 | } 256 | 257 | void FMathVMBase::Reset() 258 | { 259 | Tokens.Empty(); 260 | Statements.Empty(); 261 | } 262 | 263 | FMathVM::FMathVM() 264 | { 265 | RegisterFunction("abs", MathVM::BuiltinFunctions::Abs, MathVM::BuiltinFunctions::AbsArgs); 266 | RegisterFunction("acos", MathVM::BuiltinFunctions::ACos, MathVM::BuiltinFunctions::ACosArgs); 267 | RegisterFunction("all", MathVM::BuiltinFunctions::All, MathVM::BuiltinFunctions::AllArgs); 268 | RegisterFunction("any", MathVM::BuiltinFunctions::Any, MathVM::BuiltinFunctions::AnyArgs); 269 | RegisterFunction("asin", MathVM::BuiltinFunctions::ASin, MathVM::BuiltinFunctions::ASinArgs); 270 | RegisterFunction("atan", MathVM::BuiltinFunctions::ATan, MathVM::BuiltinFunctions::ATanArgs); 271 | RegisterFunction("ceil", MathVM::BuiltinFunctions::Ceil, MathVM::BuiltinFunctions::CeilArgs); 272 | RegisterFunction("clamp", MathVM::BuiltinFunctions::Clamp, MathVM::BuiltinFunctions::ClampArgs); 273 | RegisterFunction("cos", MathVM::BuiltinFunctions::Cos, MathVM::BuiltinFunctions::CosArgs); 274 | RegisterFunction("degrees", MathVM::BuiltinFunctions::Degrees, MathVM::BuiltinFunctions::DegreesArgs); 275 | RegisterFunction("distance", MathVM::BuiltinFunctions::Distance, MathVM::BuiltinFunctions::DistanceArgs); 276 | RegisterFunction("dot", MathVM::BuiltinFunctions::Dot, MathVM::BuiltinFunctions::DotArgs); 277 | RegisterFunction("equal", MathVM::BuiltinFunctions::Equal, MathVM::BuiltinFunctions::EqualArgs); 278 | RegisterFunction("exp", MathVM::BuiltinFunctions::Exp, MathVM::BuiltinFunctions::ExpArgs); 279 | RegisterFunction("exp2", MathVM::BuiltinFunctions::Exp2, MathVM::BuiltinFunctions::Exp2Args); 280 | RegisterFunction("floor", MathVM::BuiltinFunctions::Floor, MathVM::BuiltinFunctions::FloorArgs); 281 | RegisterFunction("fract", MathVM::BuiltinFunctions::Fract, MathVM::BuiltinFunctions::FractArgs); 282 | RegisterFunction("gradient", MathVM::BuiltinFunctions::Gradient, MathVM::BuiltinFunctions::GradientArgs); 283 | RegisterFunction("greater", MathVM::BuiltinFunctions::Greater, MathVM::BuiltinFunctions::GreaterArgs); 284 | RegisterFunction("greater_equal", MathVM::BuiltinFunctions::GreaterEqual, MathVM::BuiltinFunctions::GreaterEqualArgs); 285 | RegisterFunction("hue2b", MathVM::BuiltinFunctions::Hue2B, MathVM::BuiltinFunctions::Hue2BArgs); 286 | RegisterFunction("hue2g", MathVM::BuiltinFunctions::Hue2G, MathVM::BuiltinFunctions::Hue2GArgs); 287 | RegisterFunction("hue2r", MathVM::BuiltinFunctions::Hue2R, MathVM::BuiltinFunctions::Hue2RArgs); 288 | RegisterFunction("length", MathVM::BuiltinFunctions::Length, MathVM::BuiltinFunctions::LengthArgs); 289 | RegisterFunction("lerp", MathVM::BuiltinFunctions::Lerp, MathVM::BuiltinFunctions::LerpArgs); 290 | RegisterFunction("less", MathVM::BuiltinFunctions::Less, MathVM::BuiltinFunctions::LessArgs); 291 | RegisterFunction("less_equal", MathVM::BuiltinFunctions::LessEqual, MathVM::BuiltinFunctions::LessEqualArgs); 292 | RegisterFunction("log", MathVM::BuiltinFunctions::Log, MathVM::BuiltinFunctions::LogArgs); 293 | RegisterFunction("log10", MathVM::BuiltinFunctions::Log10, MathVM::BuiltinFunctions::Log10Args); 294 | RegisterFunction("log2", MathVM::BuiltinFunctions::Log2, MathVM::BuiltinFunctions::Log2Args); 295 | RegisterFunction("logx", MathVM::BuiltinFunctions::LogX, MathVM::BuiltinFunctions::LogXArgs); 296 | RegisterFunction("map", MathVM::BuiltinFunctions::Map, MathVM::BuiltinFunctions::MapArgs); 297 | RegisterFunction("max", MathVM::BuiltinFunctions::Max, MathVM::BuiltinFunctions::MaxArgs); 298 | RegisterFunction("mean", MathVM::BuiltinFunctions::Mean, MathVM::BuiltinFunctions::MeanArgs); 299 | RegisterFunction("min", MathVM::BuiltinFunctions::Min, MathVM::BuiltinFunctions::MinArgs); 300 | RegisterFunction("mod", MathVM::BuiltinFunctions::Mod, MathVM::BuiltinFunctions::ModArgs); 301 | RegisterFunction("not", MathVM::BuiltinFunctions::Not, MathVM::BuiltinFunctions::NotArgs); 302 | RegisterFunction("pow", MathVM::BuiltinFunctions::Pow, MathVM::BuiltinFunctions::PowArgs); 303 | RegisterFunction("radians", MathVM::BuiltinFunctions::Radians, MathVM::BuiltinFunctions::RadiansArgs); 304 | RegisterFunction("rand", MathVM::BuiltinFunctions::Rand, MathVM::BuiltinFunctions::RandArgs); 305 | RegisterFunction("round", MathVM::BuiltinFunctions::Round, MathVM::BuiltinFunctions::RoundArgs); 306 | RegisterFunction("round_even", MathVM::BuiltinFunctions::RoundEven, MathVM::BuiltinFunctions::RoundEvenArgs); 307 | RegisterFunction("sign", MathVM::BuiltinFunctions::Sign, MathVM::BuiltinFunctions::SignArgs); 308 | RegisterFunction("sin", MathVM::BuiltinFunctions::Sin, MathVM::BuiltinFunctions::SinArgs); 309 | RegisterFunction("sqrt", MathVM::BuiltinFunctions::Sqrt, MathVM::BuiltinFunctions::SqrtArgs); 310 | RegisterFunction("tan", MathVM::BuiltinFunctions::Tan, MathVM::BuiltinFunctions::TanArgs); 311 | RegisterFunction("trunc", MathVM::BuiltinFunctions::Trunc, MathVM::BuiltinFunctions::TruncArgs); 312 | 313 | // Resources functions 314 | RegisterFunction("read", MathVM::BuiltinFunctions::Read, MathVM::BuiltinFunctions::ReadArgs); 315 | RegisterFunction("write", MathVM::BuiltinFunctions::Write, MathVM::BuiltinFunctions::WriteArgs); 316 | 317 | RegisterConst("PI", UE_PI); 318 | } -------------------------------------------------------------------------------- /Source/MathVM/Private/MathVMCompiler.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | #include "MathVM.h" 4 | 5 | bool FMathVMBase::Compile() 6 | { 7 | TArray OutputQueue; 8 | TArray OperatorStack; 9 | TArray FunctionsArgsStack; 10 | TArray FunctionHasFirstArgStack; 11 | bool bLocked = false; 12 | 13 | for (const FMathVMToken& Token : Tokens) 14 | { 15 | if (Token.TokenType == EMathVMTokenType::Number || Token.TokenType == EMathVMTokenType::Variable) 16 | { 17 | if (!FunctionHasFirstArgStack.IsEmpty()) 18 | { 19 | FunctionHasFirstArgStack.Last() = true; 20 | } 21 | OutputQueue.Add(&Token); 22 | } 23 | else if (Token.TokenType == EMathVMTokenType::Function) 24 | { 25 | OperatorStack.Add(&Token); 26 | if (!FunctionHasFirstArgStack.IsEmpty()) 27 | { 28 | FunctionHasFirstArgStack.Last() = true; 29 | } 30 | FunctionHasFirstArgStack.Add(false); 31 | FunctionsArgsStack.Add(0); 32 | } 33 | else if (Token.TokenType == EMathVMTokenType::Operator) 34 | { 35 | while (!OperatorStack.IsEmpty() && OperatorStack.Last()->TokenType == EMathVMTokenType::Operator && OperatorStack.Last()->Precedence <= Token.Precedence) 36 | { 37 | OutputQueue.Add(MATHVM_POP(OperatorStack)); 38 | } 39 | OperatorStack.Add(&Token); 40 | } 41 | else if (Token.TokenType == EMathVMTokenType::Comma) 42 | { 43 | while (!OperatorStack.IsEmpty() && OperatorStack.Last()->TokenType != EMathVMTokenType::OpenParenthesis) 44 | { 45 | OutputQueue.Add(MATHVM_POP(OperatorStack)); 46 | } 47 | 48 | if (FunctionHasFirstArgStack.IsEmpty() || !FunctionHasFirstArgStack.Last()) 49 | { 50 | return SetError("Commas are expected only after the first argument of a function"); 51 | } 52 | 53 | FunctionsArgsStack.Last()++; 54 | 55 | FunctionHasFirstArgStack.Last() = false; 56 | } 57 | else if (Token.TokenType == EMathVMTokenType::OpenParenthesis) 58 | { 59 | OperatorStack.Add(&Token); 60 | } 61 | else if (Token.TokenType == EMathVMTokenType::Lock) 62 | { 63 | if (!OperatorStack.IsEmpty()) 64 | { 65 | return SetError("Unexpected Lock"); 66 | } 67 | 68 | if (bLocked) 69 | { 70 | return SetError("Lock without Unlock"); 71 | } 72 | bLocked = true; 73 | Statements.Add({ &Token }); 74 | } 75 | else if (Token.TokenType == EMathVMTokenType::Unlock) 76 | { 77 | if (!OperatorStack.IsEmpty()) 78 | { 79 | return SetError("Unexpected Unlock"); 80 | } 81 | 82 | if (!bLocked) 83 | { 84 | return SetError("Unlock without Lock"); 85 | } 86 | bLocked = false; 87 | Statements.Add({ &Token }); 88 | } 89 | else if (Token.TokenType == EMathVMTokenType::CloseParenthesis) 90 | { 91 | while (!OperatorStack.IsEmpty() && OperatorStack.Last()->TokenType != EMathVMTokenType::OpenParenthesis) 92 | { 93 | OutputQueue.Add(MATHVM_POP(OperatorStack)); 94 | } 95 | if (OperatorStack.IsEmpty() || OperatorStack.Last()->TokenType != EMathVMTokenType::OpenParenthesis) 96 | { 97 | return SetError("Expected open parenthesis"); 98 | } 99 | MATHVM_POP(OperatorStack); 100 | 101 | if (!OperatorStack.IsEmpty()) 102 | { 103 | if (OperatorStack.Last()->TokenType == EMathVMTokenType::Function) 104 | { 105 | // this is basically the only case the compiler needs a const_cast 106 | FMathVMToken* FunctionToken = const_cast(OperatorStack.Last()); 107 | 108 | FunctionToken->DetectedNumArgs = MATHVM_POP(FunctionsArgsStack) + (FunctionHasFirstArgStack.Pop() ? 1 : 0); 109 | 110 | if (FunctionToken->NumArgs >= 0 && FunctionToken->DetectedNumArgs != FunctionToken->NumArgs) 111 | { 112 | return SetError(FString::Printf(TEXT("Function %s expects %d argument%s (detected %d)"), *(FunctionToken->Value), FunctionToken->NumArgs, FunctionToken->NumArgs == 1 ? TEXT("") : TEXT("s"), FunctionToken->DetectedNumArgs)); 113 | } 114 | OutputQueue.Add(MATHVM_POP(OperatorStack)); 115 | } 116 | } 117 | } 118 | else if (Token.TokenType == EMathVMTokenType::Semicolon) 119 | { 120 | while (!OperatorStack.IsEmpty()) 121 | { 122 | if (OperatorStack.Last()->TokenType == EMathVMTokenType::OpenParenthesis) 123 | { 124 | return SetError("Mismatched parenthesis"); 125 | } 126 | OutputQueue.Add(MATHVM_POP(OperatorStack)); 127 | } 128 | Statements.Add(OutputQueue); 129 | OutputQueue.Empty(); 130 | OperatorStack.Empty(); 131 | FunctionsArgsStack.Empty(); 132 | FunctionHasFirstArgStack.Empty(); 133 | } 134 | } 135 | 136 | while (!OperatorStack.IsEmpty()) 137 | { 138 | if (OperatorStack.Last()->TokenType == EMathVMTokenType::OpenParenthesis) 139 | { 140 | return SetError("Mismatched parenthesis"); 141 | } 142 | OutputQueue.Add(MATHVM_POP(OperatorStack)); 143 | } 144 | Statements.Add(OutputQueue); 145 | 146 | if (bLocked) 147 | { 148 | return SetError("Lock without Unlock"); 149 | } 150 | 151 | return true; 152 | } -------------------------------------------------------------------------------- /Source/MathVM/Private/MathVMResourceObject.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | 4 | #include "MathVMResourceObject.h" 5 | 6 | void UMathVMResourceObject::SetMathVMResource(TSharedPtr InMathVMResource) 7 | { 8 | MathVMResource = InMathVMResource; 9 | } 10 | 11 | TSharedPtr UMathVMResourceObject::GetMathVMResource() const 12 | { 13 | return MathVMResource; 14 | } 15 | 16 | double UMathVMResourceObject::Read(const TArray& Args) 17 | { 18 | if (!MathVMResource) 19 | { 20 | return 0; 21 | } 22 | 23 | return MathVMResource->Read(Args); 24 | } 25 | 26 | void UMathVMResourceObject::Write(const TArray& Args) 27 | { 28 | if (!MathVMResource) 29 | { 30 | return; 31 | } 32 | 33 | MathVMResource->Write(Args); 34 | } -------------------------------------------------------------------------------- /Source/MathVM/Private/MathVMResources.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | #include "MathVMResources.h" 4 | #include "TextureResource.h" 5 | 6 | FMathVMTexture2DResource::FMathVMTexture2DResource(UTexture2D* Texture) 7 | { 8 | const TIndirectArray& Mips = Texture->GetPlatformMips(); 9 | if (Mips.Num() > 0) 10 | { 11 | Width = Mips[0].SizeX; 12 | Height = Mips[0].SizeY; 13 | PixelFormat = Texture->GetPixelFormat(); 14 | if (Width > 0 && Height > 0) 15 | { 16 | const void* Data = Mips[0].BulkData.LockReadOnly(); 17 | if (Data) 18 | { 19 | Pixels.AddUninitialized(Mips[0].BulkData.GetBulkDataSize()); 20 | FMemory::Memcpy(Pixels.GetData(), Data, Pixels.Num()); 21 | Mips[0].BulkData.Unlock(); 22 | } 23 | } 24 | } 25 | } 26 | 27 | double FMathVMTexture2DResource::Read(const TArray& Args) const 28 | { 29 | if (Args.Num() < 1 || Pixels.IsEmpty()) 30 | { 31 | return 0; 32 | } 33 | 34 | const int32 Channel = static_cast(Args[0]); 35 | 36 | double U = 0; 37 | double V = 0; 38 | 39 | if (Args.Num() > 1) 40 | { 41 | U = Args[1]; 42 | if (Args.Num() > 2) 43 | { 44 | V = Args[2]; 45 | } 46 | } 47 | 48 | int32 X = 0; 49 | int32 Y = 0; 50 | 51 | if (Width > 1) 52 | { 53 | X = static_cast(Width * U) % (Width - 1); 54 | } 55 | 56 | if (Height > 1) 57 | { 58 | Y = static_cast(Height * V) % (Height - 1); 59 | } 60 | 61 | switch (PixelFormat) 62 | { 63 | case(EPixelFormat::PF_B8G8R8A8): 64 | { 65 | int32 BGRChannel = Channel; 66 | if (BGRChannel == 0) 67 | { 68 | BGRChannel = 2; 69 | } 70 | else if (BGRChannel == 2) 71 | { 72 | BGRChannel = 0; 73 | } 74 | const int32 Offset = ((Y * Width + X) * 4) + BGRChannel; 75 | if (Pixels.IsValidIndex(Offset)) 76 | { 77 | return Pixels[Offset] / 255.0; 78 | } 79 | } 80 | break; 81 | case(EPixelFormat::PF_R8G8B8A8): 82 | { 83 | const int32 Offset = ((Y * Width + X) * 4) + Channel; 84 | if (Pixels.IsValidIndex(Offset)) 85 | { 86 | return Pixels[Offset] / 255.0; 87 | } 88 | } 89 | break; 90 | default: 91 | break; 92 | } 93 | return 0; 94 | } 95 | 96 | void FMathVMTexture2DResource::Write(const TArray& Args) 97 | { 98 | 99 | } 100 | 101 | FMathVMCurveBaseResource::FMathVMCurveBaseResource(UCurveBase* Curve) 102 | { 103 | Curves = Curve->GetCurves(); 104 | } 105 | 106 | double FMathVMCurveBaseResource::Read(const TArray& Args) const 107 | { 108 | if (Args.Num() < 2) 109 | { 110 | return 0; 111 | } 112 | 113 | if (Curves.IsValidIndex(Args[0])) 114 | { 115 | return Curves[Args[0]].CurveToEdit->Eval(Args[1], 0); 116 | } 117 | 118 | return 0; 119 | } 120 | 121 | void FMathVMCurveBaseResource::Write(const TArray& Args) 122 | { 123 | 124 | } 125 | 126 | FMathVMDoubleArrayResource::FMathVMDoubleArrayResource(const int32 ArraySize) 127 | { 128 | Data.AddZeroed(ArraySize); 129 | } 130 | 131 | double FMathVMDoubleArrayResource::Read(const TArray& Args) const 132 | { 133 | if (Args.Num() < 1) 134 | { 135 | return 0; 136 | } 137 | 138 | if (Data.IsValidIndex(Args[0])) 139 | { 140 | return Data[Args[0]]; 141 | } 142 | 143 | return 0; 144 | } 145 | 146 | void FMathVMDoubleArrayResource::Write(const TArray& Args) 147 | { 148 | if (Args.Num() < 2) 149 | { 150 | return; 151 | } 152 | 153 | if (Data.IsValidIndex(Args[0])) 154 | { 155 | Data[Args[0]] = Args[1]; 156 | } 157 | } 158 | 159 | FMathVMDataTableResource::FMathVMDataTableResource(UDataTable* DataTable, const TArray& FieldNames) 160 | { 161 | bool bFailed = false; 162 | for (auto RowIt = DataTable->GetRowMap().CreateConstIterator(); RowIt; ++RowIt) 163 | { 164 | if (bFailed) 165 | { 166 | return; 167 | } 168 | 169 | TArray Fields; 170 | Fields.AddZeroed(FieldNames.Num()); 171 | 172 | for (int32 FieldIndex = 0; FieldIndex < FieldNames.Num(); FieldIndex++) 173 | { 174 | const FString& FieldName = FieldNames[FieldIndex]; 175 | 176 | FProperty* Property = DataTable->FindTableProperty(*FieldName); 177 | if (!Property) 178 | { 179 | bFailed = true; 180 | break; 181 | } 182 | 183 | FNumericProperty* NumericProperty = CastField(Property); 184 | if (!NumericProperty) 185 | { 186 | bFailed = true; 187 | break; 188 | } 189 | 190 | uint8* RowData = NumericProperty->ContainerPtrToValuePtr(RowIt.Value()); 191 | 192 | if (NumericProperty->IsFloatingPoint()) 193 | { 194 | Fields[FieldIndex] = NumericProperty->GetFloatingPointPropertyValue(RowData); 195 | } 196 | else if (NumericProperty->IsInteger()) 197 | { 198 | Fields[FieldIndex] = static_cast(NumericProperty->GetSignedIntPropertyValue(RowData)); 199 | } 200 | } 201 | 202 | if (bFailed) 203 | { 204 | return; 205 | } 206 | 207 | Data.Add(MoveTemp(Fields)); 208 | } 209 | } 210 | 211 | double FMathVMDataTableResource::Read(const TArray& Args) const 212 | { 213 | if (Args.Num() < 2) 214 | { 215 | return 0; 216 | } 217 | 218 | if (Data.IsValidIndex(Args[0]) && Data[Args[0]].IsValidIndex(Args[1])) 219 | { 220 | return Data[Args[0]][Args[1]]; 221 | } 222 | 223 | return 0; 224 | } 225 | 226 | void FMathVMDataTableResource::Write(const TArray& Args) 227 | { 228 | } -------------------------------------------------------------------------------- /Source/MathVM/Private/MathVMRuntime.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | #include "MathVM.h" 4 | 5 | bool FMathVMBase::TokenizeAndCompile(const FString& Code) 6 | { 7 | if (!Tokenize(Code)) 8 | { 9 | return false; 10 | } 11 | 12 | return Compile(); 13 | } 14 | 15 | bool FMathVMBase::ExecuteStatement(FMathVMCallContext& CallContext, const TArray Statement, FString& Error) 16 | { 17 | for (const FMathVMToken* Token : Statement) 18 | { 19 | if (Token->TokenType == EMathVMTokenType::Operator) 20 | { 21 | if (!Token->Operator(CallContext)) 22 | { 23 | Error = CallContext.LastError; 24 | return false; 25 | } 26 | } 27 | else if (Token->TokenType == EMathVMTokenType::Function) 28 | { 29 | TArray Args; 30 | Args.AddUninitialized(Token->DetectedNumArgs); 31 | 32 | for (int32 ArgIndex = 0; ArgIndex < Token->DetectedNumArgs; ArgIndex++) 33 | { 34 | if (!CallContext.PopArgument(Args[(Token->DetectedNumArgs - 1) - ArgIndex])) 35 | { 36 | Error = CallContext.LastError; 37 | return false; 38 | } 39 | } 40 | if (!Token->Function(CallContext, Args)) 41 | { 42 | Error = CallContext.LastError; 43 | return false; 44 | } 45 | } 46 | else if (Token->TokenType == EMathVMTokenType::Lock) 47 | { 48 | Lock.Lock(); 49 | } 50 | else if (Token->TokenType == EMathVMTokenType::Unlock) 51 | { 52 | Lock.Unlock(); 53 | } 54 | else 55 | { 56 | CallContext.Stack.Add(Token); 57 | } 58 | } 59 | 60 | return true; 61 | } 62 | 63 | bool FMathVMBase::Execute(TMap& LocalVariables, const int32 PopResults, TArray& Results, FString& Error, void* LocalContext) 64 | { 65 | for (const TPair& LocalVariable : LocalVariables) 66 | { 67 | if (!MathVM::Utils::SanitizeName(LocalVariable.Key)) 68 | { 69 | Error = FString::Printf(TEXT("Invalid local variable name \"%s\""), *LocalVariable.Key); 70 | return false; 71 | } 72 | } 73 | 74 | FMathVMCallContext CallContext(*this, LocalVariables, LocalContext); 75 | 76 | int32 TempTokensToReserve = 0; 77 | for (const TArray& Statement : Statements) 78 | { 79 | TempTokensToReserve += Statement.Num(); 80 | } 81 | // this ensures no dangling pointers (no reallocation) 82 | CallContext.TempTokens.Reserve(TempTokensToReserve); 83 | 84 | for (const TArray& Statement : Statements) 85 | { 86 | if (!ExecuteStatement(CallContext, Statement, Error)) 87 | { 88 | return false; 89 | } 90 | } 91 | 92 | for (int32 PopIndex = 0; PopIndex < PopResults; PopIndex++) 93 | { 94 | if (CallContext.Stack.IsEmpty()) 95 | { 96 | Error = FString::Printf(TEXT("Unable to pop result %d"), PopIndex); 97 | return false; 98 | } 99 | 100 | const FMathVMToken* LastToken = MATHVM_POP(CallContext.Stack); 101 | 102 | if (LastToken->TokenType == EMathVMTokenType::Number) 103 | { 104 | Results.Add(LastToken->NumericValue); 105 | } 106 | else if (LastToken->TokenType == EMathVMTokenType::Variable) 107 | { 108 | if (LocalVariables.Contains(LastToken->Value)) 109 | { 110 | Results.Add(LocalVariables[LastToken->Value]); 111 | } 112 | else if (HasGlobalVariable(LastToken->Value)) 113 | { 114 | Results.Add(GetGlobalVariable(LastToken->Value)); 115 | } 116 | else 117 | { 118 | Error = FString::Printf(TEXT("Unset variable %s for result %d"), *(LastToken->Value), PopIndex); 119 | return false; 120 | } 121 | } 122 | else 123 | { 124 | Error = FString::Printf(TEXT("result %d is not a number"), PopIndex); 125 | return false; 126 | } 127 | } 128 | 129 | return true; 130 | } 131 | 132 | bool FMathVMBase::ExecuteAndDiscard(TMap& LocalVariables, FString& Error, void* LocalContext) 133 | { 134 | TArray EmptyResults; 135 | return Execute(LocalVariables, 0, EmptyResults, Error, LocalContext); 136 | } 137 | 138 | bool FMathVMBase::ExecuteOne(TMap& LocalVariables, double& Result, FString& Error, void* LocalContext) 139 | { 140 | TArray SingleResult; 141 | if (!Execute(LocalVariables, 1, SingleResult, Error, LocalContext)) 142 | { 143 | return false; 144 | } 145 | Result = SingleResult[0]; 146 | return true; 147 | } 148 | 149 | bool FMathVMBase::ExecuteStealth(TMap& LocalVariables, void* LocalContext) 150 | { 151 | FString DiscardedError; 152 | return ExecuteAndDiscard(LocalVariables, DiscardedError, LocalContext); 153 | } 154 | 155 | bool FMathVMCallContext::SetError(const FString& InError) 156 | { 157 | LastError = InError; 158 | return false; 159 | } 160 | 161 | bool FMathVMCallContext::PopArgument(double& Value) 162 | { 163 | if (Stack.IsEmpty()) 164 | { 165 | return false; 166 | } 167 | 168 | const FMathVMToken* Token = MATHVM_POP(Stack); 169 | 170 | if (Token->TokenType == EMathVMTokenType::Variable) 171 | { 172 | if (LocalVariables.Contains(Token->Value)) 173 | { 174 | Value = LocalVariables[Token->Value]; 175 | return true; 176 | } 177 | 178 | if (MathVM.HasConst(Token->Value)) 179 | { 180 | Value = MathVM.GetConst(Token->Value); 181 | return true; 182 | } 183 | 184 | if (MathVM.HasGlobalVariable(Token->Value)) 185 | { 186 | Value = MathVM.GetGlobalVariable(Token->Value); 187 | return true; 188 | } 189 | 190 | SetError(FString::Printf(TEXT("Unknown symbol \"%s\""), *Token->Value)); 191 | return false; 192 | } 193 | 194 | if (Token->TokenType == EMathVMTokenType::Number) 195 | { 196 | Value = Token->NumericValue; 197 | return true; 198 | } 199 | 200 | SetError("Stack corruption detected"); 201 | return false; 202 | } 203 | 204 | bool FMathVMCallContext::PopName(FString& Name) 205 | { 206 | if (Stack.IsEmpty()) 207 | { 208 | return false; 209 | } 210 | 211 | const FMathVMToken* Token = MATHVM_POP(Stack); 212 | 213 | if (Token->TokenType == EMathVMTokenType::Variable) 214 | { 215 | Name = Token->Value; 216 | return true; 217 | } 218 | 219 | SetError("Stack corruption detected"); 220 | return false; 221 | } 222 | 223 | bool FMathVMCallContext::PushResult(const double Value) 224 | { 225 | const int32 NewTempToken = TempTokens.Add(FMathVMToken(Value)); 226 | Stack.Add(&(TempTokens[NewTempToken])); 227 | return true; 228 | } 229 | 230 | double FMathVMCallContext::ReadResource(const int32 Index, const TArray& Args) 231 | { 232 | TSharedPtr Resource = MathVM.GetResource(Index); 233 | if (!Resource) 234 | { 235 | return 0; 236 | } 237 | return Resource->Read(Args); 238 | } 239 | 240 | void FMathVMCallContext::WriteResource(const int32 Index, const TArray& Args) 241 | { 242 | TSharedPtr Resource = MathVM.GetResource(Index); 243 | if (!Resource) 244 | { 245 | return; 246 | } 247 | Resource->Write(Args); 248 | } -------------------------------------------------------------------------------- /Source/MathVM/Private/MathVMTokenizer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | #include "MathVM.h" 4 | 5 | bool FMathVMBase::Tokenize(const FString& Code) 6 | { 7 | const int32 CodeLen = Code.Len(); 8 | Accumulator = ""; 9 | double NumberMultiplier = 1; 10 | bool bIsInComment = false; 11 | 12 | CurrentLine = 1; 13 | CurrentOffset = 0; 14 | Tokens.Empty(); 15 | 16 | while (CurrentOffset < CodeLen) 17 | { 18 | TCHAR Char = Code[CurrentOffset++]; 19 | 20 | if (Char == '\n') 21 | { 22 | CurrentLine++; 23 | } 24 | 25 | if (bIsInComment) 26 | { 27 | if (Char == '\n' || Char == '#') 28 | { 29 | bIsInComment = false; 30 | } 31 | continue; 32 | } 33 | 34 | const bool bStartsWithPoint = Char == '.'; 35 | 36 | if ((Char >= '0' && Char <= '9') || bStartsWithPoint) 37 | { 38 | // number after variable/function name? 39 | if (!Accumulator.IsEmpty() && !bStartsWithPoint) 40 | { 41 | Accumulator += Char; 42 | continue; 43 | } 44 | 45 | if (!CheckAndResetAccumulator()) 46 | { 47 | return false; 48 | } 49 | 50 | FString NumberAccumulator = ""; 51 | NumberAccumulator += Char; 52 | 53 | while (CurrentOffset < CodeLen) 54 | { 55 | Char = Code[CurrentOffset]; 56 | if (Char >= '0' && Char <= '9') 57 | { 58 | NumberAccumulator += Char; 59 | } 60 | else 61 | { 62 | break; 63 | } 64 | 65 | CurrentOffset++; 66 | } 67 | 68 | if (Char == '.' && !bStartsWithPoint) 69 | { 70 | NumberAccumulator += Char; 71 | CurrentOffset++; 72 | while (CurrentOffset < CodeLen) 73 | { 74 | Char = Code[CurrentOffset]; 75 | if (Char >= '0' && Char <= '9') 76 | { 77 | NumberAccumulator += Char; 78 | } 79 | else 80 | { 81 | break; 82 | } 83 | 84 | CurrentOffset++; 85 | } 86 | } 87 | 88 | if (Char == 'e' || Char == 'E') 89 | { 90 | NumberAccumulator += Char; 91 | CurrentOffset++; 92 | Char = Code[CurrentOffset]; 93 | 94 | if (Char == '+' || Char == '-') 95 | { 96 | NumberAccumulator += Char; 97 | CurrentOffset++; 98 | while (CurrentOffset < CodeLen) 99 | { 100 | Char = Code[CurrentOffset]; 101 | if (Char >= '0' && Char <= '9') 102 | { 103 | NumberAccumulator += Char; 104 | } 105 | else 106 | { 107 | break; 108 | } 109 | 110 | CurrentOffset++; 111 | } 112 | } 113 | } 114 | 115 | const double NumericValue = FCString::Atod(*NumberAccumulator); 116 | if (!AddToken(FMathVMToken(NumericValue * NumberMultiplier))) 117 | { 118 | return false; 119 | } 120 | NumberMultiplier = 1; 121 | } 122 | else if (Char == '-') 123 | { 124 | if (!HasPreviousToken()) 125 | { 126 | NumberMultiplier = -1; 127 | } 128 | else if (GetPreviousToken().TokenType == EMathVMTokenType::OpenParenthesis || GetPreviousToken().TokenType == EMathVMTokenType::Operator || GetPreviousToken().TokenType == EMathVMTokenType::Comma || GetPreviousToken().TokenType == EMathVMTokenType::Semicolon) 129 | { 130 | NumberMultiplier = -1; 131 | } 132 | else 133 | { 134 | if (!AddToken(FMathVMToken(OperatorSub, 6))) 135 | { 136 | return false; 137 | } 138 | NumberMultiplier = 1; 139 | } 140 | } 141 | else if (Char == '+') 142 | { 143 | if (!HasPreviousToken()) 144 | { 145 | NumberMultiplier = 1; 146 | } 147 | else if (GetPreviousToken().TokenType == EMathVMTokenType::OpenParenthesis || GetPreviousToken().TokenType == EMathVMTokenType::Operator || GetPreviousToken().TokenType == EMathVMTokenType::Comma || GetPreviousToken().TokenType == EMathVMTokenType::Semicolon) 148 | { 149 | NumberMultiplier = 1; 150 | } 151 | else 152 | { 153 | if (!AddToken(FMathVMToken(OperatorAdd, 6))) 154 | { 155 | return false; 156 | } 157 | NumberMultiplier = 1; 158 | } 159 | } 160 | else if (Char == '*') 161 | { 162 | if (!CheckAndResetAccumulator() || !AddToken(FMathVMToken(OperatorMul, 5))) 163 | { 164 | return false; 165 | } 166 | NumberMultiplier = 1; 167 | } 168 | else if (Char == '/') 169 | { 170 | if (!CheckAndResetAccumulator() || !AddToken(FMathVMToken(OperatorDiv, 5))) 171 | { 172 | return false; 173 | } 174 | NumberMultiplier = 1; 175 | } 176 | else if (Char == '%') 177 | { 178 | if (!CheckAndResetAccumulator() || !AddToken(FMathVMToken(OperatorMod, 5))) 179 | { 180 | return false; 181 | } 182 | NumberMultiplier = 1; 183 | } 184 | else if (Char == '=') 185 | { 186 | if (!CheckAndResetAccumulator() || !AddToken(FMathVMToken(OperatorAssign, 17))) 187 | { 188 | return false; 189 | } 190 | NumberMultiplier = 1; 191 | } 192 | else if (Char == '(') 193 | { 194 | if (!CheckAndResetAccumulator() || !AddToken(FMathVMToken(EMathVMTokenType::OpenParenthesis))) 195 | { 196 | return false; 197 | } 198 | NumberMultiplier = 1; 199 | } 200 | else if (Char == ')') 201 | { 202 | if (!CheckAndResetAccumulator() || !AddToken(FMathVMToken(EMathVMTokenType::CloseParenthesis))) 203 | { 204 | return false; 205 | } 206 | NumberMultiplier = 1; 207 | } 208 | else if (Char == '{') 209 | { 210 | if (!CheckAndResetAccumulator() || !AddToken(FMathVMToken(EMathVMTokenType::Lock))) 211 | { 212 | return false; 213 | } 214 | NumberMultiplier = 1; 215 | } 216 | else if (Char == '}') 217 | { 218 | if (!CheckAndResetAccumulator() || !AddToken(FMathVMToken(EMathVMTokenType::Unlock))) 219 | { 220 | return false; 221 | } 222 | NumberMultiplier = 1; 223 | } 224 | else if (Char == ',') 225 | { 226 | if (!CheckAndResetAccumulator() || !AddToken(FMathVMToken(EMathVMTokenType::Comma))) 227 | { 228 | return false; 229 | } 230 | NumberMultiplier = 1; 231 | } 232 | else if (Char == ';') 233 | { 234 | if (!CheckAndResetAccumulator() || !AddToken(FMathVMToken(EMathVMTokenType::Semicolon))) 235 | { 236 | return false; 237 | } 238 | NumberMultiplier = 1; 239 | } 240 | else if (Char == ' ' || Char == '\t' || Char == '\r' || Char == '\n') 241 | { 242 | if (!CheckAndResetAccumulator()) 243 | { 244 | return false; 245 | } 246 | NumberMultiplier = 1; 247 | } 248 | else if (Char == '#') 249 | { 250 | if (!CheckAndResetAccumulator()) 251 | { 252 | return false; 253 | } 254 | NumberMultiplier = 1; 255 | bIsInComment = true; 256 | } 257 | else if ((Char >= 'A' && Char <= 'Z') || (Char >= 'a' && Char <= 'z') || Char == '_') 258 | { 259 | Accumulator += Char; 260 | NumberMultiplier = 1; 261 | } 262 | else 263 | { 264 | return SetError(FString::Printf(TEXT("Unexpected char: %c"), Char)); 265 | } 266 | } 267 | 268 | return CheckAccumulator(true); 269 | } 270 | 271 | bool FMathVMBase::CheckAndResetAccumulator() 272 | { 273 | bool bSuccess = CheckAccumulator(false); 274 | Accumulator = ""; 275 | return bSuccess; 276 | } 277 | 278 | bool FMathVMBase::CheckAccumulator(const bool bLastCheck) 279 | { 280 | if (Accumulator.IsEmpty()) 281 | { 282 | return true; 283 | } 284 | 285 | if (Functions.Contains(Accumulator)) 286 | { 287 | if (bLastCheck) 288 | { 289 | return SetError(FString::Printf(TEXT("Expected open parenthesis after function %s"), *Accumulator)); 290 | } 291 | 292 | return AddToken(FMathVMToken(Accumulator, Functions[Accumulator].Key, Functions[Accumulator].Value)); 293 | } 294 | 295 | return AddToken(FMathVMToken(EMathVMTokenType::Variable, Accumulator)); 296 | } 297 | 298 | bool FMathVMBase::AddToken(const FMathVMToken& Token) 299 | { 300 | if (!Tokens.IsEmpty()) 301 | { 302 | if (Tokens.Last().TokenType == EMathVMTokenType::Function && Token.TokenType != EMathVMTokenType::OpenParenthesis) 303 | { 304 | return SetError(FString::Printf(TEXT("Expected open parenthesis after function %s"), *Token.Value)); 305 | } 306 | } 307 | 308 | Tokens.Add(Token); 309 | return true; 310 | } -------------------------------------------------------------------------------- /Source/MathVM/Private/Tests/MathVMBuiltinFunctionsTests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 - Roberto De Ioris. 2 | 3 | #if WITH_DEV_AUTOMATION_TESTS 4 | #include "MathVM.h" 5 | #include "Misc/AutomationTest.h" 6 | 7 | // this is just a hack for allowing compilation on UE < 5.3, the test suite is meant to be run on UE >= 5.3 8 | #if ENGINE_MINOR_VERSION < 3 9 | static bool TestNearlyEqual(const TCHAR* What, const double A, const double B) 10 | { 11 | return false; 12 | } 13 | #endif 14 | 15 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_ATan, "MathVMBuiltinFunctions.ATan", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 16 | 17 | bool MathVMBuiltinFunctions_ATan::RunTest(const FString& Parameters) 18 | { 19 | FMathVM MathVM; 20 | MathVM.TokenizeAndCompile("atan(PI / 2)"); 21 | 22 | TMap LocalVariables; 23 | double Result = 0; 24 | FString Error; 25 | 26 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 27 | 28 | TestNearlyEqual(TEXT("Result"), Result, 1.0038848218538872); 29 | 30 | return true; 31 | } 32 | 33 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Abs, "MathVMBuiltinFunctions.Abs", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 34 | 35 | bool MathVMBuiltinFunctions_Abs::RunTest(const FString& Parameters) 36 | { 37 | FMathVM MathVM; 38 | MathVM.TokenizeAndCompile("abs(-1.1)"); 39 | 40 | TMap LocalVariables; 41 | double Result = 0; 42 | FString Error; 43 | 44 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 45 | 46 | TestNearlyEqual(TEXT("Result"), Result, 1.1); 47 | 48 | return true; 49 | } 50 | 51 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_ACos, "MathVMBuiltinFunctions.ACos", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 52 | 53 | bool MathVMBuiltinFunctions_ACos::RunTest(const FString& Parameters) 54 | { 55 | FMathVM MathVM; 56 | MathVM.TokenizeAndCompile("acos(0.5)"); 57 | 58 | TMap LocalVariables; 59 | double Result = 0; 60 | FString Error; 61 | 62 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 63 | 64 | TestNearlyEqual(TEXT("Result"), Result, 1.0471975511965979); 65 | 66 | return true; 67 | } 68 | 69 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_All, "MathVMBuiltinFunctions.All", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 70 | 71 | bool MathVMBuiltinFunctions_All::RunTest(const FString& Parameters) 72 | { 73 | FMathVM MathVM; 74 | MathVM.TokenizeAndCompile("all(1, 2, 3, 4, 5)"); 75 | 76 | TMap LocalVariables; 77 | double Result = 0; 78 | FString Error; 79 | 80 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 81 | 82 | TestEqual(TEXT("Result"), Result, 1.0); 83 | 84 | return true; 85 | } 86 | 87 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_AllFails, "MathVMBuiltinFunctions.AllFails", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 88 | 89 | bool MathVMBuiltinFunctions_AllFails::RunTest(const FString& Parameters) 90 | { 91 | FMathVM MathVM; 92 | MathVM.TokenizeAndCompile("all(1, 2, 0, 4, 5)"); 93 | 94 | TMap LocalVariables; 95 | double Result = 0; 96 | FString Error; 97 | 98 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 99 | 100 | TestEqual(TEXT("Result"), Result, 0.0); 101 | 102 | return true; 103 | } 104 | 105 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Ceil, "MathVMBuiltinFunctions.Ceil", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 106 | 107 | bool MathVMBuiltinFunctions_Ceil::RunTest(const FString& Parameters) 108 | { 109 | FMathVM MathVM; 110 | MathVM.TokenizeAndCompile("ceil(1.1)"); 111 | 112 | TMap LocalVariables; 113 | double Result = 0; 114 | FString Error; 115 | 116 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 117 | 118 | TestNearlyEqual(TEXT("Result"), Result, 2); 119 | 120 | return true; 121 | } 122 | 123 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Ceil1, "MathVMBuiltinFunctions.Ceil1", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 124 | 125 | bool MathVMBuiltinFunctions_Ceil1::RunTest(const FString& Parameters) 126 | { 127 | FMathVM MathVM; 128 | MathVM.TokenizeAndCompile("ceil(3)"); 129 | 130 | TMap LocalVariables; 131 | double Result = 0; 132 | FString Error; 133 | 134 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 135 | 136 | TestNearlyEqual(TEXT("Result"), Result, 3); 137 | 138 | return true; 139 | } 140 | 141 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Trunc, "MathVMBuiltinFunctions.Trunc", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 142 | 143 | bool MathVMBuiltinFunctions_Trunc::RunTest(const FString& Parameters) 144 | { 145 | FMathVM MathVM; 146 | MathVM.TokenizeAndCompile("trunc(3.99)"); 147 | 148 | TMap LocalVariables; 149 | double Result = 0; 150 | FString Error; 151 | 152 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 153 | 154 | TestEqual(TEXT("Result"), Result, 3.0); 155 | 156 | return true; 157 | } 158 | 159 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Sqrt, "MathVMBuiltinFunctions.Sqrt", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 160 | 161 | bool MathVMBuiltinFunctions_Sqrt::RunTest(const FString& Parameters) 162 | { 163 | FMathVM MathVM; 164 | MathVM.TokenizeAndCompile("sqrt(4)"); 165 | 166 | TMap LocalVariables; 167 | double Result = 0; 168 | FString Error; 169 | 170 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 171 | 172 | TestEqual(TEXT("Result"), Result, 2.0); 173 | 174 | return true; 175 | } 176 | 177 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Radians, "MathVMBuiltinFunctions.Radians", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 178 | 179 | bool MathVMBuiltinFunctions_Radians::RunTest(const FString& Parameters) 180 | { 181 | FMathVM MathVM; 182 | MathVM.TokenizeAndCompile("radians(180)"); 183 | 184 | TMap LocalVariables; 185 | double Result = 0; 186 | FString Error; 187 | 188 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 189 | 190 | TestNearlyEqual(TEXT("Result"), Result, static_cast(UE_PI)); 191 | 192 | return true; 193 | } 194 | 195 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Min, "MathVMBuiltinFunctions.Min", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 196 | 197 | bool MathVMBuiltinFunctions_Min::RunTest(const FString& Parameters) 198 | { 199 | FMathVM MathVM; 200 | MathVM.TokenizeAndCompile("min(5, 3, 4, 5, 2.1, 9)"); 201 | 202 | TMap LocalVariables; 203 | double Result = 0; 204 | FString Error; 205 | 206 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 207 | 208 | TestEqual(TEXT("Result"), Result, 2.1); 209 | 210 | return true; 211 | } 212 | 213 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Mean, "MathVMBuiltinFunctions.Mean", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 214 | 215 | bool MathVMBuiltinFunctions_Mean::RunTest(const FString& Parameters) 216 | { 217 | FMathVM MathVM; 218 | MathVM.TokenizeAndCompile("mean(3, 3, 3, 3)"); 219 | 220 | TMap LocalVariables; 221 | double Result = 0; 222 | FString Error; 223 | 224 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 225 | 226 | TestEqual(TEXT("Result"), Result, 3.0); 227 | 228 | return true; 229 | } 230 | 231 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Max, "MathVMBuiltinFunctions.Max", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 232 | 233 | bool MathVMBuiltinFunctions_Max::RunTest(const FString& Parameters) 234 | { 235 | FMathVM MathVM; 236 | MathVM.TokenizeAndCompile("max(5, 3, 99, 4, 5, 2.1, 9)"); 237 | 238 | TMap LocalVariables; 239 | double Result = 0; 240 | FString Error; 241 | 242 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 243 | 244 | TestEqual(TEXT("Result"), Result, 99.0); 245 | 246 | return true; 247 | } 248 | 249 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Fract, "MathVMBuiltinFunctions.Fract", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 250 | 251 | bool MathVMBuiltinFunctions_Fract::RunTest(const FString& Parameters) 252 | { 253 | FMathVM MathVM; 254 | MathVM.TokenizeAndCompile("fract(300.123)"); 255 | 256 | TMap LocalVariables; 257 | double Result = 0; 258 | FString Error; 259 | 260 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 261 | 262 | TestEqual(TEXT("Result"), Result, 0.123); 263 | 264 | return true; 265 | } 266 | 267 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Pow, "MathVMBuiltinFunctions.Pow", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 268 | 269 | bool MathVMBuiltinFunctions_Pow::RunTest(const FString& Parameters) 270 | { 271 | FMathVM MathVM; 272 | MathVM.TokenizeAndCompile("pow(3, 2)"); 273 | 274 | TMap LocalVariables; 275 | double Result = 0; 276 | FString Error; 277 | 278 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 279 | 280 | TestEqual(TEXT("Result"), Result, 9.0); 281 | 282 | return true; 283 | } 284 | 285 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Sign, "MathVMBuiltinFunctions.Sign", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 286 | 287 | bool MathVMBuiltinFunctions_Sign::RunTest(const FString& Parameters) 288 | { 289 | FMathVM MathVM; 290 | MathVM.TokenizeAndCompile("sign(-3)"); 291 | 292 | TMap LocalVariables; 293 | double Result = 0; 294 | FString Error; 295 | 296 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 297 | 298 | TestEqual(TEXT("Result"), Result, -1.0); 299 | 300 | return true; 301 | } 302 | 303 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_SignZero, "MathVMBuiltinFunctions.SignZero", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 304 | 305 | bool MathVMBuiltinFunctions_SignZero::RunTest(const FString& Parameters) 306 | { 307 | FMathVM MathVM; 308 | MathVM.TokenizeAndCompile("sign(0)"); 309 | 310 | TMap LocalVariables; 311 | double Result = 0; 312 | FString Error; 313 | 314 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 315 | 316 | TestEqual(TEXT("Result"), Result, 0.0); 317 | 318 | return true; 319 | } 320 | 321 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Any, "MathVMBuiltinFunctions.Any", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 322 | 323 | bool MathVMBuiltinFunctions_Any::RunTest(const FString& Parameters) 324 | { 325 | FMathVM MathVM; 326 | MathVM.TokenizeAndCompile("any(-3, 0, 0, 0)"); 327 | 328 | TMap LocalVariables; 329 | double Result = 0; 330 | FString Error; 331 | 332 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 333 | 334 | TestEqual(TEXT("Result"), Result, 1.0); 335 | 336 | return true; 337 | } 338 | 339 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Any2, "MathVMBuiltinFunctions.Any2", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 340 | 341 | bool MathVMBuiltinFunctions_Any2::RunTest(const FString& Parameters) 342 | { 343 | FMathVM MathVM; 344 | MathVM.TokenizeAndCompile("any(0, 0, 0, 0)"); 345 | 346 | TMap LocalVariables; 347 | double Result = 0; 348 | FString Error; 349 | 350 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 351 | 352 | TestEqual(TEXT("Result"), Result, 0.0); 353 | 354 | return true; 355 | } 356 | 357 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Equal, "MathVMBuiltinFunctions.Equal", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 358 | 359 | bool MathVMBuiltinFunctions_Equal::RunTest(const FString& Parameters) 360 | { 361 | FMathVM MathVM; 362 | MathVM.TokenizeAndCompile("equal(1, 1, 1, 1)"); 363 | 364 | TMap LocalVariables; 365 | double Result = 0; 366 | FString Error; 367 | 368 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 369 | 370 | TestEqual(TEXT("Result"), Result, 1.0); 371 | 372 | return true; 373 | } 374 | 375 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_EqualNot, "MathVMBuiltinFunctions.EqualNot", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 376 | 377 | bool MathVMBuiltinFunctions_EqualNot::RunTest(const FString& Parameters) 378 | { 379 | FMathVM MathVM; 380 | MathVM.TokenizeAndCompile("equal(1, 0, 1, 1)"); 381 | 382 | TMap LocalVariables; 383 | double Result = 0; 384 | FString Error; 385 | 386 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 387 | 388 | TestEqual(TEXT("Result"), Result, 0.0); 389 | 390 | return true; 391 | } 392 | 393 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Clamp, "MathVMBuiltinFunctions.Clamp", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 394 | 395 | bool MathVMBuiltinFunctions_Clamp::RunTest(const FString& Parameters) 396 | { 397 | FMathVM MathVM; 398 | MathVM.TokenizeAndCompile("clamp(100, 0, 1)"); 399 | 400 | TMap LocalVariables; 401 | double Result = 0; 402 | FString Error; 403 | 404 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 405 | 406 | TestEqual(TEXT("Result"), Result, 1.0); 407 | 408 | return true; 409 | } 410 | 411 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Sin, "MathVMBuiltinFunctions.Sin", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 412 | 413 | bool MathVMBuiltinFunctions_Sin::RunTest(const FString& Parameters) 414 | { 415 | FMathVM MathVM; 416 | MathVM.TokenizeAndCompile("sin(PI)"); 417 | 418 | TMap LocalVariables; 419 | double Result = 0; 420 | FString Error; 421 | 422 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 423 | 424 | TestEqual(TEXT("Result"), Result, 0.0); 425 | 426 | return true; 427 | } 428 | 429 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Not, "MathVMBuiltinFunctions.Not", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 430 | 431 | bool MathVMBuiltinFunctions_Not::RunTest(const FString& Parameters) 432 | { 433 | FMathVM MathVM; 434 | MathVM.TokenizeAndCompile("not(3)"); 435 | 436 | TMap LocalVariables; 437 | double Result = 0; 438 | FString Error; 439 | 440 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 441 | 442 | TestEqual(TEXT("Result"), Result, 0.0); 443 | 444 | return true; 445 | } 446 | 447 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_NotZero, "MathVMBuiltinFunctions.NotZero", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 448 | 449 | bool MathVMBuiltinFunctions_NotZero::RunTest(const FString& Parameters) 450 | { 451 | FMathVM MathVM; 452 | MathVM.TokenizeAndCompile("not(0)"); 453 | 454 | TMap LocalVariables; 455 | double Result = 0; 456 | FString Error; 457 | 458 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 459 | 460 | TestEqual(TEXT("Result"), Result, 1.0); 461 | 462 | return true; 463 | } 464 | 465 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_NotNeg, "MathVMBuiltinFunctions.NotNeg", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 466 | 467 | bool MathVMBuiltinFunctions_NotNeg::RunTest(const FString& Parameters) 468 | { 469 | FMathVM MathVM; 470 | MathVM.TokenizeAndCompile("not(-10)"); 471 | 472 | TMap LocalVariables; 473 | double Result = 0; 474 | FString Error; 475 | 476 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 477 | 478 | TestEqual(TEXT("Result"), Result, 0.0); 479 | 480 | return true; 481 | } 482 | 483 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Length, "MathVMBuiltinFunctions.Length", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 484 | 485 | bool MathVMBuiltinFunctions_Length::RunTest(const FString& Parameters) 486 | { 487 | FMathVM MathVM; 488 | MathVM.TokenizeAndCompile("length(1, 2, 3)"); 489 | 490 | TMap LocalVariables; 491 | double Result = 0; 492 | FString Error; 493 | 494 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 495 | 496 | TestNearlyEqual(TEXT("Result"), Result, 3.7416573867739413); 497 | 498 | return true; 499 | } 500 | 501 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Degrees, "MathVMBuiltinFunctions.Degrees", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 502 | 503 | bool MathVMBuiltinFunctions_Degrees::RunTest(const FString& Parameters) 504 | { 505 | FMathVM MathVM; 506 | MathVM.TokenizeAndCompile("degrees(PI)"); 507 | 508 | TMap LocalVariables; 509 | double Result = 0; 510 | FString Error; 511 | 512 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 513 | 514 | TestNearlyEqual(TEXT("Result"), Result, 180.0); 515 | 516 | return true; 517 | } 518 | 519 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Lerp, "MathVMBuiltinFunctions.Lerp", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 520 | 521 | bool MathVMBuiltinFunctions_Lerp::RunTest(const FString& Parameters) 522 | { 523 | FMathVM MathVM; 524 | MathVM.TokenizeAndCompile("lerp(0, 100, 0.5)"); 525 | 526 | TMap LocalVariables; 527 | double Result = 0; 528 | FString Error; 529 | 530 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 531 | 532 | TestNearlyEqual(TEXT("Result"), Result, 50.0); 533 | 534 | return true; 535 | } 536 | 537 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Floor, "MathVMBuiltinFunctions.Floor", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 538 | 539 | bool MathVMBuiltinFunctions_Floor::RunTest(const FString& Parameters) 540 | { 541 | FMathVM MathVM; 542 | MathVM.TokenizeAndCompile("floor(1.9)"); 543 | 544 | TMap LocalVariables; 545 | double Result = 0; 546 | FString Error; 547 | 548 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 549 | 550 | TestNearlyEqual(TEXT("Result"), Result, 1.0); 551 | 552 | return true; 553 | } 554 | 555 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_FloorZero, "MathVMBuiltinFunctions.FloorZero", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 556 | 557 | bool MathVMBuiltinFunctions_FloorZero::RunTest(const FString& Parameters) 558 | { 559 | FMathVM MathVM; 560 | MathVM.TokenizeAndCompile("floor(0.7)"); 561 | 562 | TMap LocalVariables; 563 | double Result = 0; 564 | FString Error; 565 | 566 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 567 | 568 | TestNearlyEqual(TEXT("Result"), Result, 0.0); 569 | 570 | return true; 571 | } 572 | 573 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Cos, "MathVMBuiltinFunctions.Cos", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 574 | 575 | bool MathVMBuiltinFunctions_Cos::RunTest(const FString& Parameters) 576 | { 577 | FMathVM MathVM; 578 | MathVM.TokenizeAndCompile("cos(0)"); 579 | 580 | TMap LocalVariables; 581 | double Result = 0; 582 | FString Error; 583 | 584 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 585 | 586 | TestNearlyEqual(TEXT("Result"), Result, 1.0); 587 | 588 | return true; 589 | } 590 | 591 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Less, "MathVMBuiltinFunctions.Less", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 592 | 593 | bool MathVMBuiltinFunctions_Less::RunTest(const FString& Parameters) 594 | { 595 | FMathVM MathVM; 596 | MathVM.TokenizeAndCompile("less(0, 1)"); 597 | 598 | TMap LocalVariables; 599 | double Result = 0; 600 | FString Error; 601 | 602 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 603 | 604 | TestNearlyEqual(TEXT("Result"), Result, 1.0); 605 | 606 | return true; 607 | } 608 | 609 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_LessNot, "MathVMBuiltinFunctions.LessNot", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 610 | 611 | bool MathVMBuiltinFunctions_LessNot::RunTest(const FString& Parameters) 612 | { 613 | FMathVM MathVM; 614 | MathVM.TokenizeAndCompile("less(3, 1)"); 615 | 616 | TMap LocalVariables; 617 | double Result = 0; 618 | FString Error; 619 | 620 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 621 | 622 | TestNearlyEqual(TEXT("Result"), Result, 0.0); 623 | 624 | return true; 625 | } 626 | 627 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_LessEqual, "MathVMBuiltinFunctions.LessEqual", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 628 | 629 | bool MathVMBuiltinFunctions_LessEqual::RunTest(const FString& Parameters) 630 | { 631 | FMathVM MathVM; 632 | MathVM.TokenizeAndCompile("less_equal(1, 1)"); 633 | 634 | TMap LocalVariables; 635 | double Result = 0; 636 | FString Error; 637 | 638 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 639 | 640 | TestNearlyEqual(TEXT("Result"), Result, 1.0); 641 | 642 | return true; 643 | } 644 | 645 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Gradient, "MathVMBuiltinFunctions.Gradient", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 646 | 647 | bool MathVMBuiltinFunctions_Gradient::RunTest(const FString& Parameters) 648 | { 649 | FMathVM MathVM; 650 | MathVM.TokenizeAndCompile("gradient(0.75, 0, 1, 0.5, 100, 1, 1000)"); 651 | 652 | TMap LocalVariables; 653 | double Result = 0; 654 | FString Error; 655 | 656 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 657 | 658 | TestNearlyEqual(TEXT("Result"), Result, 550.0); 659 | 660 | return true; 661 | } 662 | 663 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_RoundEven, "MathVMBuiltinFunctions.RoundEven", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 664 | 665 | bool MathVMBuiltinFunctions_RoundEven::RunTest(const FString& Parameters) 666 | { 667 | FMathVM MathVM; 668 | MathVM.TokenizeAndCompile("round_even(3.5)"); 669 | 670 | TMap LocalVariables; 671 | double Result = 0; 672 | FString Error; 673 | 674 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 675 | 676 | TestNearlyEqual(TEXT("Result"), Result, 4.0); 677 | 678 | return true; 679 | } 680 | 681 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_RoundEven2, "MathVMBuiltinFunctions.RoundEven2", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 682 | 683 | bool MathVMBuiltinFunctions_RoundEven2::RunTest(const FString& Parameters) 684 | { 685 | FMathVM MathVM; 686 | MathVM.TokenizeAndCompile("round_even(4.5)"); 687 | 688 | TMap LocalVariables; 689 | double Result = 0; 690 | FString Error; 691 | 692 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 693 | 694 | TestNearlyEqual(TEXT("Result"), Result, 4.0); 695 | 696 | return true; 697 | } 698 | 699 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Exp, "MathVMBuiltinFunctions.Exp", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 700 | 701 | bool MathVMBuiltinFunctions_Exp::RunTest(const FString& Parameters) 702 | { 703 | FMathVM MathVM; 704 | MathVM.TokenizeAndCompile("exp(2)"); 705 | 706 | TMap LocalVariables; 707 | double Result = 0; 708 | FString Error; 709 | 710 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 711 | 712 | TestNearlyEqual(TEXT("Result"), Result, 7.38905609893065); 713 | 714 | return true; 715 | } 716 | 717 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Exp2, "MathVMBuiltinFunctions.Exp2", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 718 | 719 | bool MathVMBuiltinFunctions_Exp2::RunTest(const FString& Parameters) 720 | { 721 | FMathVM MathVM; 722 | MathVM.TokenizeAndCompile("exp2(8)"); 723 | 724 | TMap LocalVariables; 725 | double Result = 0; 726 | FString Error; 727 | 728 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 729 | 730 | TestNearlyEqual(TEXT("Result"), Result, 256.0); 731 | 732 | return true; 733 | } 734 | 735 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Greater, "MathVMBuiltinFunctions.Greater", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 736 | 737 | bool MathVMBuiltinFunctions_Greater::RunTest(const FString& Parameters) 738 | { 739 | FMathVM MathVM; 740 | MathVM.TokenizeAndCompile("greater(5, 1)"); 741 | 742 | TMap LocalVariables; 743 | double Result = 0; 744 | FString Error; 745 | 746 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 747 | 748 | TestNearlyEqual(TEXT("Result"), Result, 1.0); 749 | 750 | return true; 751 | } 752 | 753 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_GreaterNot, "MathVMBuiltinFunctions.GreaterNot", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 754 | 755 | bool MathVMBuiltinFunctions_GreaterNot::RunTest(const FString& Parameters) 756 | { 757 | FMathVM MathVM; 758 | MathVM.TokenizeAndCompile("greater(2, 5)"); 759 | 760 | TMap LocalVariables; 761 | double Result = 0; 762 | FString Error; 763 | 764 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 765 | 766 | TestNearlyEqual(TEXT("Result"), Result, 0.0); 767 | 768 | return true; 769 | } 770 | 771 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_GreaterEqual, "MathVMBuiltinFunctions.GreaterEqual", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 772 | 773 | bool MathVMBuiltinFunctions_GreaterEqual::RunTest(const FString& Parameters) 774 | { 775 | FMathVM MathVM; 776 | MathVM.TokenizeAndCompile("greater_equal(5, 5)"); 777 | 778 | TMap LocalVariables; 779 | double Result = 0; 780 | FString Error; 781 | 782 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 783 | 784 | TestNearlyEqual(TEXT("Result"), Result, 1.0); 785 | 786 | return true; 787 | } 788 | 789 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_GreaterEqualNot, "MathVMBuiltinFunctions.GreaterEqualNot", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 790 | 791 | bool MathVMBuiltinFunctions_GreaterEqualNot::RunTest(const FString& Parameters) 792 | { 793 | FMathVM MathVM; 794 | MathVM.TokenizeAndCompile("greater_equal(4, 5)"); 795 | 796 | TMap LocalVariables; 797 | double Result = 0; 798 | FString Error; 799 | 800 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 801 | 802 | TestNearlyEqual(TEXT("Result"), Result, 0.0); 803 | 804 | return true; 805 | } 806 | 807 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Rand, "MathVMBuiltinFunctions.Rand", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 808 | 809 | bool MathVMBuiltinFunctions_Rand::RunTest(const FString& Parameters) 810 | { 811 | FMathVM MathVM; 812 | MathVM.TokenizeAndCompile("rand(0, 1)"); 813 | 814 | TMap LocalVariables; 815 | double Result = 0; 816 | FString Error; 817 | 818 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 819 | 820 | TestTrue(TEXT("Result"), Result >= 0.0 && Result <= 1.0); 821 | 822 | return true; 823 | } 824 | 825 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_RandZero, "MathVMBuiltinFunctions.RandZero", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 826 | 827 | bool MathVMBuiltinFunctions_RandZero::RunTest(const FString& Parameters) 828 | { 829 | FMathVM MathVM; 830 | MathVM.TokenizeAndCompile("rand(0, 0)"); 831 | 832 | TMap LocalVariables; 833 | double Result = 0; 834 | FString Error; 835 | 836 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 837 | 838 | TestNearlyEqual(TEXT("Result"), Result, 0.0); 839 | 840 | return true; 841 | } 842 | 843 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_ASin, "MathVMBuiltinFunctions.ASin", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 844 | 845 | bool MathVMBuiltinFunctions_ASin::RunTest(const FString& Parameters) 846 | { 847 | FMathVM MathVM; 848 | MathVM.TokenizeAndCompile("asin(1)"); 849 | 850 | TMap LocalVariables; 851 | double Result = 0; 852 | FString Error; 853 | 854 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 855 | 856 | TestNearlyEqual(TEXT("Result"), Result, 1.5707963267948966); 857 | 858 | return true; 859 | } 860 | 861 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Map, "MathVMBuiltinFunctions.Map", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 862 | 863 | bool MathVMBuiltinFunctions_Map::RunTest(const FString& Parameters) 864 | { 865 | FMathVM MathVM; 866 | MathVM.TokenizeAndCompile("map(0.5, 0, 100, 1, 200)"); 867 | 868 | TMap LocalVariables; 869 | double Result = 0; 870 | FString Error; 871 | 872 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 873 | 874 | TestNearlyEqual(TEXT("Result"), Result, 150.0); 875 | 876 | return true; 877 | } 878 | 879 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Distance, "MathVMBuiltinFunctions.Distance", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 880 | 881 | bool MathVMBuiltinFunctions_Distance::RunTest(const FString& Parameters) 882 | { 883 | FMathVM MathVM; 884 | MathVM.TokenizeAndCompile("distance(2, 2, 2, 3, 3, 3)"); 885 | 886 | TMap LocalVariables; 887 | double Result = 0; 888 | FString Error; 889 | 890 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 891 | 892 | TestNearlyEqual(TEXT("Result"), Result, 1.7320508075688772); 893 | 894 | return true; 895 | } 896 | 897 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Dot, "MathVMBuiltinFunctions.Dot", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 898 | 899 | bool MathVMBuiltinFunctions_Dot::RunTest(const FString& Parameters) 900 | { 901 | FMathVM MathVM; 902 | MathVM.TokenizeAndCompile("dot(1, 0, 0, 1, 0, 0)"); 903 | 904 | TMap LocalVariables; 905 | double Result = 0; 906 | FString Error; 907 | 908 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 909 | 910 | TestNearlyEqual(TEXT("Result"), Result, 1); 911 | 912 | return true; 913 | } 914 | 915 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Log, "MathVMBuiltinFunctions.Log", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 916 | 917 | bool MathVMBuiltinFunctions_Log::RunTest(const FString& Parameters) 918 | { 919 | FMathVM MathVM; 920 | MathVM.TokenizeAndCompile("log(10)"); 921 | 922 | TMap LocalVariables; 923 | double Result = 0; 924 | FString Error; 925 | 926 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 927 | 928 | TestNearlyEqual(TEXT("Result"), Result, 2.302585092994046); 929 | 930 | return true; 931 | } 932 | 933 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Log2, "MathVMBuiltinFunctions.Log2", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 934 | 935 | bool MathVMBuiltinFunctions_Log2::RunTest(const FString& Parameters) 936 | { 937 | FMathVM MathVM; 938 | MathVM.TokenizeAndCompile("log2(10)"); 939 | 940 | TMap LocalVariables; 941 | double Result = 0; 942 | FString Error; 943 | 944 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 945 | 946 | TestNearlyEqual(TEXT("Result"), Result, 3.321928094887362); 947 | 948 | return true; 949 | } 950 | 951 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Log10, "MathVMBuiltinFunctions.Log10", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 952 | 953 | bool MathVMBuiltinFunctions_Log10::RunTest(const FString& Parameters) 954 | { 955 | FMathVM MathVM; 956 | MathVM.TokenizeAndCompile("log10(3)"); 957 | 958 | TMap LocalVariables; 959 | double Result = 0; 960 | FString Error; 961 | 962 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 963 | 964 | TestNearlyEqual(TEXT("Result"), Result, 0.47712125471966244); 965 | 966 | return true; 967 | } 968 | 969 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_LogX, "MathVMBuiltinFunctions.LogX", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 970 | 971 | bool MathVMBuiltinFunctions_LogX::RunTest(const FString& Parameters) 972 | { 973 | FMathVM MathVM; 974 | MathVM.TokenizeAndCompile("logx(3, 4)"); 975 | 976 | TMap LocalVariables; 977 | double Result = 0; 978 | FString Error; 979 | 980 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 981 | 982 | TestNearlyEqual(TEXT("Result"), Result, 1.2618595071429148); 983 | 984 | return true; 985 | } 986 | 987 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Tan, "MathVMBuiltinFunctions.Tan", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 988 | 989 | bool MathVMBuiltinFunctions_Tan::RunTest(const FString& Parameters) 990 | { 991 | FMathVM MathVM; 992 | MathVM.TokenizeAndCompile("tan(1)"); 993 | 994 | TMap LocalVariables; 995 | double Result = 0; 996 | FString Error; 997 | 998 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 999 | 1000 | TestNearlyEqual(TEXT("Result"), Result, 1.5574077246549023); 1001 | 1002 | return true; 1003 | } 1004 | 1005 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Round, "MathVMBuiltinFunctions.Round", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 1006 | 1007 | bool MathVMBuiltinFunctions_Round::RunTest(const FString& Parameters) 1008 | { 1009 | FMathVM MathVM; 1010 | MathVM.TokenizeAndCompile("round(2.5)"); 1011 | 1012 | TMap LocalVariables; 1013 | double Result = 0; 1014 | FString Error; 1015 | 1016 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 1017 | 1018 | TestNearlyEqual(TEXT("Result"), Result, 3.0); 1019 | 1020 | return true; 1021 | } 1022 | 1023 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Round2, "MathVMBuiltinFunctions.Round2", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 1024 | 1025 | bool MathVMBuiltinFunctions_Round2::RunTest(const FString& Parameters) 1026 | { 1027 | FMathVM MathVM; 1028 | MathVM.TokenizeAndCompile("round(2.4)"); 1029 | 1030 | TMap LocalVariables; 1031 | double Result = 0; 1032 | FString Error; 1033 | 1034 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 1035 | 1036 | TestNearlyEqual(TEXT("Result"), Result, 2.0); 1037 | 1038 | return true; 1039 | } 1040 | 1041 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Mod, "MathVMBuiltinFunctions.Mod", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 1042 | 1043 | bool MathVMBuiltinFunctions_Mod::RunTest(const FString& Parameters) 1044 | { 1045 | FMathVM MathVM; 1046 | MathVM.TokenizeAndCompile("mod(1, 2)"); 1047 | 1048 | TMap LocalVariables; 1049 | double Result = 0; 1050 | FString Error; 1051 | 1052 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 1053 | 1054 | TestNearlyEqual(TEXT("Result"), Result, 1.0); 1055 | 1056 | return true; 1057 | } 1058 | 1059 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Hue2B, "MathVMBuiltinFunctions.Hue2B", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 1060 | 1061 | bool MathVMBuiltinFunctions_Hue2B::RunTest(const FString& Parameters) 1062 | { 1063 | FMathVM MathVM; 1064 | MathVM.TokenizeAndCompile("hue2b(1)"); 1065 | 1066 | TMap LocalVariables; 1067 | double Result = 0; 1068 | FString Error; 1069 | 1070 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 1071 | 1072 | TestNearlyEqual(TEXT("Result"), Result, 0); 1073 | 1074 | return true; 1075 | } 1076 | 1077 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Hue2G, "MathVMBuiltinFunctions.Hue2G", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 1078 | 1079 | bool MathVMBuiltinFunctions_Hue2G::RunTest(const FString& Parameters) 1080 | { 1081 | FMathVM MathVM; 1082 | MathVM.TokenizeAndCompile("hue2g(1)"); 1083 | 1084 | TMap LocalVariables; 1085 | double Result = 0; 1086 | FString Error; 1087 | 1088 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 1089 | 1090 | TestNearlyEqual(TEXT("Result"), Result, 0); 1091 | 1092 | return true; 1093 | } 1094 | 1095 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(MathVMBuiltinFunctions_Hue2R, "MathVMBuiltinFunctions.Hue2R", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 1096 | 1097 | bool MathVMBuiltinFunctions_Hue2R::RunTest(const FString& Parameters) 1098 | { 1099 | FMathVM MathVM; 1100 | MathVM.TokenizeAndCompile("hue2r(1)"); 1101 | 1102 | TMap LocalVariables; 1103 | double Result = 0; 1104 | FString Error; 1105 | 1106 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 1107 | 1108 | TestNearlyEqual(TEXT("Result"), Result, 1); 1109 | 1110 | return true; 1111 | } 1112 | #endif 1113 | -------------------------------------------------------------------------------- /Source/MathVM/Private/Tests/MathVMResourcesTests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 - Roberto De Ioris. 2 | 3 | #if WITH_DEV_AUTOMATION_TESTS 4 | #include "MathVM.h" 5 | #include "MathVMResources.h" 6 | #include "Misc/AutomationTest.h" 7 | 8 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathVMResourcesTest_WriteDoubles, "MathVMResources.WriteDoubles", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 9 | 10 | bool FMathVMResourcesTest_WriteDoubles::RunTest(const FString& Parameters) 11 | { 12 | FMathVM MathVM; 13 | MathVM.RegisterResource(MakeShared(10)); 14 | MathVM.TokenizeAndCompile("write(0, 0, 1); write(0, 1, 2); write(0, 2, 100); write(0, 3, -200); read(0, 0); read(0, 1); read(0, 2); read(0, 3)"); 15 | 16 | TMap LocalVariables; 17 | TArray Results; 18 | FString Error; 19 | 20 | TestTrue(TEXT("bSuccess"), MathVM.Execute(LocalVariables, 4, Results, Error)); 21 | 22 | TestEqual(TEXT("Results"), Results.Num(), 4); 23 | 24 | TestEqual(TEXT("Results[0]"), Results[0], -200.0); 25 | TestEqual(TEXT("Results[1]"), Results[1], 100.0); 26 | TestEqual(TEXT("Results[2]"), Results[2], 2.0); 27 | TestEqual(TEXT("Results[3]"), Results[3], 1.0); 28 | 29 | return true; 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /Source/MathVM/Private/Tests/MathVMTests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024-2025 - Roberto De Ioris. 2 | 3 | #if WITH_DEV_AUTOMATION_TESTS 4 | #include "MathVM.h" 5 | #include "Async/ParallelFor.h" 6 | #include "Misc/AutomationTest.h" 7 | 8 | 9 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathVMTest_Empty, "MathVM.Empty", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 10 | 11 | bool FMathVMTest_Empty::RunTest(const FString& Parameters) 12 | { 13 | FMathVM MathVM; 14 | MathVM.TokenizeAndCompile("17"); 15 | 16 | TMap LocalVariables; 17 | double Result = 0; 18 | FString Error; 19 | 20 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 21 | 22 | TestEqual(TEXT("Result"), Result, 17.0); 23 | 24 | return true; 25 | } 26 | 27 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathVMTest_Garbage, "MathVM.Garbage", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 28 | 29 | bool FMathVMTest_Garbage::RunTest(const FString& Parameters) 30 | { 31 | FMathVM MathVM; 32 | 33 | TestFalse(TEXT("bSuccess"), MathVM.TokenizeAndCompile("?")); 34 | 35 | return true; 36 | } 37 | 38 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathVMTest_Add, "MathVM.Add", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 39 | 40 | bool FMathVMTest_Add::RunTest(const FString& Parameters) 41 | { 42 | FMathVM MathVM; 43 | MathVM.TokenizeAndCompile("1 + 2"); 44 | 45 | TMap LocalVariables; 46 | double Result = 0; 47 | FString Error; 48 | 49 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 50 | 51 | TestEqual(TEXT("Result"), Result, 3.0); 52 | 53 | return true; 54 | } 55 | 56 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathVMTest_Precedence, "MathVM.Precedence", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 57 | 58 | bool FMathVMTest_Precedence::RunTest(const FString& Parameters) 59 | { 60 | FMathVM MathVM; 61 | MathVM.TokenizeAndCompile("(1 + 2) * 3"); 62 | 63 | TMap LocalVariables; 64 | double Result = 0; 65 | FString Error; 66 | 67 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 68 | 69 | TestEqual(TEXT("Result"), Result, 9.0); 70 | 71 | return true; 72 | } 73 | 74 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathVMTest_Assignment, "MathVM.Assignment", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 75 | 76 | bool FMathVMTest_Assignment::RunTest(const FString& Parameters) 77 | { 78 | FMathVM MathVM; 79 | MathVM.TokenizeAndCompile("x = 17"); 80 | 81 | TMap LocalVariables; 82 | FString Error; 83 | LocalVariables.Add("x", -1); 84 | 85 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteAndDiscard(LocalVariables, Error)); 86 | 87 | TestEqual(TEXT("x"), LocalVariables["x"], 17.0); 88 | 89 | return true; 90 | } 91 | 92 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathVMTest_IncrementVar, "MathVM.IncrementVar", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 93 | 94 | bool FMathVMTest_IncrementVar::RunTest(const FString& Parameters) 95 | { 96 | FMathVM MathVM; 97 | MathVM.TokenizeAndCompile("x = 17; x = x + 5"); 98 | 99 | TMap LocalVariables; 100 | FString Error; 101 | LocalVariables.Add("x", -1); 102 | 103 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteAndDiscard(LocalVariables, Error)); 104 | 105 | TestEqual(TEXT("x"), LocalVariables["x"], 22.0); 106 | 107 | return true; 108 | } 109 | 110 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathVMTest_VarWithNum, "MathVM.VarWithNum", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 111 | 112 | bool FMathVMTest_VarWithNum::RunTest(const FString& Parameters) 113 | { 114 | FMathVM MathVM; 115 | MathVM.TokenizeAndCompile("x17 = 1000.123"); 116 | 117 | TMap LocalVariables; 118 | FString Error; 119 | 120 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteAndDiscard(LocalVariables, Error)); 121 | 122 | TestEqual(TEXT("x"), LocalVariables["x17"], 1000.123); 123 | 124 | return true; 125 | } 126 | 127 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathVMTest_HeavyChain, "MathVM.HeavyChain", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 128 | 129 | bool FMathVMTest_HeavyChain::RunTest(const FString& Parameters) 130 | { 131 | FMathVM MathVM; 132 | MathVM.TokenizeAndCompile("pow(0, 2);sin(cos(sin(cos(cos(5)))))"); 133 | 134 | TMap LocalVariables; 135 | FString Error; 136 | 137 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteAndDiscard(LocalVariables, Error)); 138 | 139 | return true; 140 | } 141 | 142 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathVMTest_Comment, "MathVM.Comment", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 143 | 144 | bool FMathVMTest_Comment::RunTest(const FString& Parameters) 145 | { 146 | FMathVM MathVM; 147 | MathVM.TokenizeAndCompile("# hello # 17"); 148 | 149 | TMap LocalVariables; 150 | double Result = 0; 151 | FString Error; 152 | 153 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 154 | 155 | TestEqual(TEXT("Result"), Result, 17.0); 156 | 157 | return true; 158 | } 159 | 160 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathVMTest_CommentNewLine, "MathVM.CommentNewLine", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 161 | 162 | bool FMathVMTest_CommentNewLine::RunTest(const FString& Parameters) 163 | { 164 | FMathVM MathVM; 165 | MathVM.TokenizeAndCompile("# hello \n# test# 22"); 166 | 167 | TMap LocalVariables; 168 | double Result = 0; 169 | FString Error; 170 | 171 | TestTrue(TEXT("bSuccess"), MathVM.ExecuteOne(LocalVariables, Result, Error)); 172 | 173 | TestEqual(TEXT("Result"), Result, 22.0); 174 | 175 | return true; 176 | } 177 | 178 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathVMTest_Multi, "MathVM.Multi", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 179 | 180 | bool FMathVMTest_Multi::RunTest(const FString& Parameters) 181 | { 182 | FMathVM MathVM; 183 | MathVM.TokenizeAndCompile("1;2;3"); 184 | 185 | TMap LocalVariables; 186 | TArray Results; 187 | FString Error; 188 | 189 | TestTrue(TEXT("bSuccess"), MathVM.Execute(LocalVariables, 3, Results, Error)); 190 | 191 | TestEqual(TEXT("Results"), Results.Num(), 3); 192 | 193 | TestEqual(TEXT("Results[0]"), Results[0], 3.0); 194 | TestEqual(TEXT("Results[1]"), Results[1], 2.0); 195 | TestEqual(TEXT("Results[2]"), Results[2], 1.0); 196 | 197 | return true; 198 | } 199 | 200 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathVMTest_ParallelWithLock, "MathVM.ParallelWithLock", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) 201 | 202 | bool FMathVMTest_ParallelWithLock::RunTest(const FString& Parameters) 203 | { 204 | FMathVM MathVM; 205 | MathVM.RegisterGlobalVariable("x", 0); 206 | MathVM.TokenizeAndCompile("{x = x + i;}"); 207 | 208 | ParallelFor(10, [&](const int32 Index) 209 | { 210 | TMap LocalVariables; 211 | LocalVariables.Add("i", Index); 212 | 213 | MathVM.ExecuteStealth(LocalVariables); 214 | }); 215 | 216 | TestEqual(TEXT("x"), MathVM.GetGlobalVariable("x"), 45.0); 217 | 218 | return true; 219 | } 220 | 221 | #endif 222 | -------------------------------------------------------------------------------- /Source/MathVM/Public/MathVM.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | #include "Runtime/Launch/Resources/Version.h" 8 | 9 | #define MATHVM_ARGS FMathVMCallContext& CallContext, const TArray& Args 10 | #define MATHVM_LAMBDA [](MATHVM_ARGS) -> bool 11 | #define MATHVM_LAMBDA_THIS [this](MATHVM_ARGS) -> bool 12 | #define MATHVM_RETURN(x) return CallContext.PushResult(x) 13 | #define MATHVM_ERROR(x) return CallContext.SetError(x) 14 | 15 | #if ENGINE_MINOR_VERSION >= 5 16 | #define MATHVM_POP(x) x.Pop(EAllowShrinking::No) 17 | #else 18 | #define MATHVM_POP(x) x.Pop(false) 19 | #endif 20 | 21 | enum class EMathVMTokenType : uint8 22 | { 23 | Number, 24 | Operator, 25 | Function, 26 | OpenParenthesis, 27 | CloseParenthesis, 28 | Comma, 29 | Variable, 30 | Semicolon, 31 | Lock, 32 | Unlock 33 | }; 34 | 35 | class FMathVMBase; 36 | struct FMathVMCallContext; 37 | 38 | struct MATHVM_API FMathVMToken 39 | { 40 | FMathVMToken() = delete; 41 | 42 | FMathVMToken(const EMathVMTokenType InTokenType) : NumericValue(0), Operator(nullptr), Function(nullptr), Precedence(0), NumArgs(0), TokenType(InTokenType) 43 | { 44 | 45 | } 46 | 47 | FMathVMToken(const EMathVMTokenType InTokenType, const FString& InValue) : NumericValue(0), Operator(nullptr), Function(nullptr), Precedence(0), NumArgs(0), Value(InValue), TokenType(InTokenType) 48 | { 49 | 50 | } 51 | 52 | // Number 53 | FMathVMToken(const double InNumericValue) : NumericValue(InNumericValue), Operator(nullptr), Function(nullptr), Precedence(0), NumArgs(0), TokenType(EMathVMTokenType::Number) 54 | { 55 | 56 | } 57 | 58 | // Operator 59 | FMathVMToken(TFunction Callable, const int32 InPrecedence) : NumericValue(0), Operator(Callable), Function(nullptr), Precedence(InPrecedence), NumArgs(0), TokenType(EMathVMTokenType::Operator) 60 | { 61 | 62 | } 63 | 64 | // Function 65 | FMathVMToken(const FString& InValue, TFunction& Args)> Callable, const int32 InNumArgs) : NumericValue(0), Operator(nullptr), Function(Callable), Precedence(0), NumArgs(InNumArgs), Value(InValue), TokenType(EMathVMTokenType::Function) 66 | { 67 | 68 | } 69 | 70 | const double NumericValue; 71 | const TFunction Operator; 72 | const TFunction& Args)> Function; 73 | const int32 Precedence; 74 | const int32 NumArgs; 75 | int32 DetectedNumArgs = 0; 76 | const FString Value; 77 | const EMathVMTokenType TokenType; 78 | }; 79 | 80 | using FMathVMStack = TArray; 81 | using FMathVMFunction = TFunction& Args)>; 82 | using FMathVMOperator = TFunction; 83 | 84 | class MATHVM_API IMathVMResource 85 | { 86 | public: 87 | virtual ~IMathVMResource() = default; 88 | virtual double Read(const TArray& Args) const = 0; 89 | virtual void Write(const TArray& Args) = 0; 90 | }; 91 | 92 | namespace MathVM 93 | { 94 | namespace Utils 95 | { 96 | bool MATHVM_API SanitizeName(const FString& Name); 97 | } 98 | } 99 | 100 | class MATHVM_API FMathVMBase 101 | { 102 | 103 | public: 104 | FMathVMBase(); 105 | 106 | virtual ~FMathVMBase() = default; 107 | 108 | bool Tokenize(const FString& Code); 109 | 110 | bool Compile(); 111 | 112 | bool TokenizeAndCompile(const FString& Code); 113 | 114 | bool Execute(TMap& LocalVariables, const int32 PopResults, TArray& Results, FString& Error, void* LocalContext = nullptr); 115 | 116 | bool ExecuteAndDiscard(TMap& LocalVariables, FString& Error, void* LocalContext = nullptr); 117 | 118 | bool ExecuteOne(TMap& LocalVariables, double& Result, FString& Error, void* LocalContext = nullptr); 119 | 120 | bool ExecuteStealth(TMap& LocalVariables, void* LocalContext = nullptr); 121 | 122 | const FString& GetError() const; 123 | 124 | bool RegisterFunction(const FString& Name, FMathVMFunction Callable, const int32 NumArgs); 125 | 126 | bool RegisterGlobalVariable(const FString& Name, const double Value); 127 | 128 | bool RegisterConst(const FString& Name, const double Value); 129 | 130 | bool HasConst(const FString& Name) const; 131 | 132 | double GetConst(const FString& Name); 133 | 134 | bool HasGlobalVariable(const FString& Name) const; 135 | 136 | void SetGlobalVariable(const FString& Name, const double Value); 137 | double GetGlobalVariable(const FString& Name) const; 138 | 139 | int32 RegisterResource(TSharedPtr Resource); 140 | 141 | TSharedPtr GetResource(const int32 Index) const; 142 | 143 | const TMap& GetGlobalVariables() const; 144 | 145 | void Reset(); 146 | 147 | protected: 148 | 149 | bool SetError(const FString& InError); 150 | 151 | bool HasPreviousToken() const 152 | { 153 | return Tokens.Num() > 0; 154 | } 155 | 156 | const FMathVMToken& GetPreviousToken() const 157 | { 158 | return Tokens.Last(); 159 | } 160 | 161 | bool ExecuteStatement(FMathVMCallContext& CallContext, const TArray Statement, FString& Error); 162 | 163 | bool CheckAndResetAccumulator(); 164 | 165 | bool CheckAccumulator(const bool bLastCheck); 166 | 167 | bool AddToken(const FMathVMToken& Token); 168 | 169 | TArray Tokens; 170 | FString LastError; 171 | 172 | int32 CurrentLine; 173 | int32 CurrentOffset; 174 | 175 | FString Accumulator; 176 | 177 | TMap> Functions; 178 | 179 | FMathVMOperator OperatorAdd; 180 | FMathVMOperator OperatorSub; 181 | FMathVMOperator OperatorMul; 182 | FMathVMOperator OperatorDiv; 183 | FMathVMOperator OperatorMod; 184 | FMathVMOperator OperatorAssign; 185 | 186 | TMap Constants; 187 | 188 | TMap GlobalVariables; 189 | 190 | TArray> Statements; 191 | 192 | TArray> Resources; 193 | 194 | FCriticalSection Lock; 195 | }; 196 | 197 | class MATHVM_API FMathVM : public FMathVMBase 198 | { 199 | public: 200 | FMathVM(); 201 | FMathVM(const FMathVM& Other) = delete; 202 | FMathVM(FMathVM&& Other) = delete; 203 | }; 204 | 205 | struct MATHVM_API FMathVMCallContext 206 | { 207 | FMathVMBase& MathVM; 208 | FMathVMStack Stack; 209 | TMap& LocalVariables; 210 | TArray TempTokens; 211 | FString LastError; 212 | void* LocalContext = nullptr; 213 | 214 | FMathVMCallContext() = delete; 215 | FMathVMCallContext(const FMathVMCallContext& Other) = delete; 216 | FMathVMCallContext(FMathVMCallContext&& Other) = delete; 217 | 218 | FMathVMCallContext(FMathVMBase& InMathVM, TMap& InLocalVariables, void* InLocalContext) : MathVM(InMathVM), LocalVariables(InLocalVariables), LocalContext(InLocalContext) 219 | { 220 | 221 | } 222 | 223 | bool SetError(const FString& InError); 224 | 225 | bool PopArgument(double& Value); 226 | 227 | bool PopName(FString& Name); 228 | 229 | bool PushResult(const double Value); 230 | 231 | double ReadResource(const int32 Index, const TArray& Args); 232 | 233 | void WriteResource(const int32 Index, const TArray& Args); 234 | }; 235 | 236 | class FMathVMModule : public IModuleInterface 237 | { 238 | public: 239 | 240 | /** IModuleInterface implementation */ 241 | virtual void StartupModule() override; 242 | virtual void ShutdownModule() override; 243 | }; 244 | -------------------------------------------------------------------------------- /Source/MathVM/Public/MathVMBlueprintFunctionLibrary.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Engine/Font.h" 7 | #include "Engine/TextureRenderTarget2D.h" 8 | #include "Kismet/BlueprintFunctionLibrary.h" 9 | #include "Kismet/KismetRenderingLibrary.h" 10 | #include "Layout/Margin.h" 11 | #include "MathVMResourceObject.h" 12 | #include "MathVMBlueprintFunctionLibrary.generated.h" 13 | 14 | namespace MathVM 15 | { 16 | namespace BlueprintUtility 17 | { 18 | MATHVM_API bool RegisterResources(FMathVM& MathVM, const TArray& Resources, FString& Error); 19 | } 20 | } 21 | 22 | UENUM() 23 | enum class EMathVMPlotterShape : uint8 24 | { 25 | Line, 26 | Box, 27 | LineAndPoint 28 | }; 29 | 30 | USTRUCT(BlueprintType) 31 | struct FMathVMPlot 32 | { 33 | GENERATED_BODY() 34 | 35 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 36 | EMathVMPlotterShape Shape; 37 | 38 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 39 | FLinearColor Color; 40 | 41 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 42 | float Thickness; 43 | 44 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 45 | FLinearColor Color2; 46 | 47 | FMathVMPlot() 48 | { 49 | Shape = EMathVMPlotterShape::Line; 50 | Color = FLinearColor::Black; 51 | Color2 = FLinearColor::Black; 52 | Thickness = 1; 53 | } 54 | }; 55 | 56 | USTRUCT(BlueprintType) 57 | struct FMathVMText 58 | { 59 | GENERATED_BODY() 60 | 61 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 62 | FString Text; 63 | 64 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 65 | FVector2D Position; 66 | 67 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 68 | FLinearColor Color; 69 | 70 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 71 | UFont* Font; 72 | 73 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 74 | FVector2D Scaling; 75 | 76 | 77 | FMathVMText() 78 | { 79 | Color = FLinearColor::Black; 80 | Position = FVector2D::ZeroVector; 81 | Font = nullptr; 82 | Scaling = FVector2D::One(); 83 | } 84 | }; 85 | 86 | USTRUCT(BlueprintType) 87 | struct FMathVMPlotterConfig 88 | { 89 | GENERATED_BODY() 90 | 91 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 92 | UTextureRenderTarget2D* RenderTarget; 93 | 94 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 95 | int32 TextureWidth; 96 | 97 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 98 | int32 TextureHeight; 99 | 100 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 101 | FLinearColor BackgroundColor; 102 | 103 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 104 | FMargin BorderSize; 105 | 106 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 107 | FLinearColor BorderColor; 108 | 109 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 110 | float BorderThickness; 111 | 112 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 113 | float GridVerticalScale; 114 | 115 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 116 | float GridThickness; 117 | 118 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MathVM") 119 | FLinearColor GridColor; 120 | 121 | FMathVMPlotterConfig() 122 | { 123 | RenderTarget = nullptr; 124 | TextureWidth = 1024; 125 | TextureHeight = 1024; 126 | BackgroundColor = FLinearColor::White; 127 | BorderSize = FMargin(2, 2, 2, 2); 128 | BorderColor = FLinearColor::Black; 129 | BorderThickness = 1; 130 | GridVerticalScale = 0; 131 | GridThickness = 1; 132 | GridColor = FLinearColor::Gray; 133 | } 134 | }; 135 | 136 | USTRUCT(BlueprintType) 137 | struct FMathVMEvaluationResult 138 | { 139 | GENERATED_BODY() 140 | 141 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "MathVM") 142 | bool bSuccess; 143 | 144 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "MathVM") 145 | FString Error; 146 | 147 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "MathVM") 148 | TMap GlobalVariables; 149 | 150 | FMathVMEvaluationResult() 151 | { 152 | bSuccess = false; 153 | } 154 | 155 | FMathVMEvaluationResult(const FString& InError) 156 | { 157 | bSuccess = false; 158 | Error = InError; 159 | } 160 | 161 | FMathVMEvaluationResult(const TMap& InGlobalVariables) 162 | { 163 | bSuccess = true; 164 | GlobalVariables = InGlobalVariables; 165 | } 166 | }; 167 | 168 | DECLARE_DYNAMIC_DELEGATE_TwoParams(FMathVMEvaluated, const bool, bSuccess, const FString&, Error); 169 | DECLARE_DYNAMIC_DELEGATE_OneParam(FMathVMEvaluatedWithResult, const FMathVMEvaluationResult&, EvaluationResult); 170 | DECLARE_DYNAMIC_DELEGATE_TwoParams(FMathVMPlotGenerated, UTexture*, Texture, const FMathVMEvaluationResult&, EvaluationResult); 171 | 172 | /** 173 | * 174 | */ 175 | UCLASS() 176 | class MATHVM_API UMathVMBlueprintFunctionLibrary : public UBlueprintFunctionLibrary 177 | { 178 | GENERATED_BODY() 179 | 180 | public: 181 | UFUNCTION(BlueprintCallable, Category = "MathVM") 182 | static UMathVMResourceObject* MathVMResourceObjectFromTexture2D(UTexture2D* Texture); 183 | 184 | UFUNCTION(BlueprintCallable, Category = "MathVM") 185 | static UMathVMResourceObject* MathVMResourceObjectFromCurveBase(UCurveBase* Curve); 186 | 187 | UFUNCTION(BlueprintCallable, Category = "MathVM") 188 | static UMathVMResourceObject* MathVMResourceObjectAsDoubleArray(const int32 ArraySize); 189 | 190 | UFUNCTION(BlueprintCallable, Category = "MathVM") 191 | static UMathVMResourceObject* MathVMResourceObjectFromDataTable(UDataTable* DataTable, const TArray& FieldNames); 192 | 193 | UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "LocalVariables,Resources"), Category = "MathVM") 194 | static bool MathVMRunSimple(const FString& Code, UPARAM(ref) TMap& LocalVariables, const TArray& Resources, double& Result, FString& Error); 195 | 196 | UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "LocalVariables,Resources"), Category = "MathVM") 197 | static bool MathVMRunSimpleMulti(const FString& Code, UPARAM(ref) TMap& LocalVariables, const TArray& Resources, const int32 PopResults, TArray& Results, FString& Error); 198 | 199 | UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "GlobalVariables,Constants,Resources"), Category = "MathVM") 200 | static void MathVMRun(const FString& Code, const TMap& GlobalVariables, const TMap& Constants, const TArray& Resources, const FMathVMEvaluatedWithResult& OnEvaluated, const int32 NumSamples = 1, const FString& SampleLocalVariable = "i"); 201 | 202 | UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject", AutoCreateRefTerm = "TextsToPlot,Constants,GlobalVariables,Resources,PlotterConfig"), Category = "MathVM") 203 | static void MathVMPlotter(UObject* WorldContextObject, const FString& Code, const int32 NumSamples, const TMap& VariablesToPlot, const TArray& TextsToPlot, const TMap& Constants, const TMap& GlobalVariables, const TArray& Resources, const FMathVMPlotGenerated& OnPlotGenerated, const FMathVMPlotterConfig& PlotterConfig, const double DomainMin = 0, const double DomainMax = 1, const FString& SampleLocalVariable = "i"); 204 | }; 205 | -------------------------------------------------------------------------------- /Source/MathVM/Public/MathVMBuiltinFunctions.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | #pragma once 4 | 5 | #include "MathVM.h" 6 | 7 | namespace MathVM 8 | { 9 | namespace BuiltinFunctions 10 | { 11 | MATHVM_API bool Abs(MATHVM_ARGS); constexpr int32 AbsArgs = 1; 12 | MATHVM_API bool ACos(MATHVM_ARGS); constexpr int32 ACosArgs = 1; 13 | MATHVM_API bool All(MATHVM_ARGS); constexpr int32 AllArgs = -1; 14 | MATHVM_API bool Any(MATHVM_ARGS); constexpr int32 AnyArgs = -1; 15 | MATHVM_API bool ASin(MATHVM_ARGS); constexpr int32 ASinArgs = 1; 16 | MATHVM_API bool ATan(MATHVM_ARGS); constexpr int32 ATanArgs = 1; 17 | MATHVM_API bool Ceil(MATHVM_ARGS); constexpr int32 CeilArgs = 1; 18 | MATHVM_API bool Clamp(MATHVM_ARGS); constexpr int32 ClampArgs = 3; 19 | MATHVM_API bool Cos(MATHVM_ARGS); constexpr int32 CosArgs = 1; 20 | MATHVM_API bool Degrees(MATHVM_ARGS); constexpr int32 DegreesArgs = 1; 21 | MATHVM_API bool Distance(MATHVM_ARGS); constexpr int32 DistanceArgs = -1; 22 | MATHVM_API bool Dot(MATHVM_ARGS); constexpr int32 DotArgs = -1; 23 | MATHVM_API bool Equal(MATHVM_ARGS); constexpr int32 EqualArgs = -1; 24 | MATHVM_API bool Exp(MATHVM_ARGS); constexpr int32 ExpArgs = 1; 25 | MATHVM_API bool Exp2(MATHVM_ARGS); constexpr int32 Exp2Args = 1; 26 | MATHVM_API bool Floor(MATHVM_ARGS); constexpr int32 FloorArgs = 1; 27 | MATHVM_API bool Fract(MATHVM_ARGS); constexpr int32 FractArgs = 1; 28 | MATHVM_API bool Gradient(MATHVM_ARGS); constexpr int32 GradientArgs = -1; 29 | MATHVM_API bool Greater(MATHVM_ARGS); constexpr int32 GreaterArgs = 2; 30 | MATHVM_API bool GreaterEqual(MATHVM_ARGS); constexpr int32 GreaterEqualArgs = 2; 31 | MATHVM_API bool Hue2B(MATHVM_ARGS); constexpr int32 Hue2BArgs = 1; 32 | MATHVM_API bool Hue2G(MATHVM_ARGS); constexpr int32 Hue2GArgs = 1; 33 | MATHVM_API bool Hue2R(MATHVM_ARGS); constexpr int32 Hue2RArgs = 1; 34 | MATHVM_API bool Length(MATHVM_ARGS); constexpr int32 LengthArgs = -1; 35 | MATHVM_API bool Lerp(MATHVM_ARGS); constexpr int32 LerpArgs = 3; 36 | MATHVM_API bool Less(MATHVM_ARGS); constexpr int32 LessArgs = 2; 37 | MATHVM_API bool LessEqual(MATHVM_ARGS); constexpr int32 LessEqualArgs = 2; 38 | MATHVM_API bool Log(MATHVM_ARGS); constexpr int32 LogArgs = 1; 39 | MATHVM_API bool Log10(MATHVM_ARGS); constexpr int32 Log10Args = 1; 40 | MATHVM_API bool Log2(MATHVM_ARGS); constexpr int32 Log2Args = 1; 41 | MATHVM_API bool LogX(MATHVM_ARGS); constexpr int32 LogXArgs = 2; 42 | MATHVM_API bool Map(MATHVM_ARGS); constexpr int32 MapArgs = 5; 43 | MATHVM_API bool Max(MATHVM_ARGS); constexpr int32 MaxArgs = -1; 44 | MATHVM_API bool Mean(MATHVM_ARGS); constexpr int32 MeanArgs = -1; 45 | MATHVM_API bool Min(MATHVM_ARGS); constexpr int32 MinArgs = -1; 46 | MATHVM_API bool Mod(MATHVM_ARGS); constexpr int32 ModArgs = 2; 47 | MATHVM_API bool Not(MATHVM_ARGS); constexpr int32 NotArgs = 1; 48 | MATHVM_API bool Pow(MATHVM_ARGS); constexpr int32 PowArgs = 2; 49 | MATHVM_API bool Radians(MATHVM_ARGS); constexpr int32 RadiansArgs = 1; 50 | MATHVM_API bool Rand(MATHVM_ARGS); constexpr int32 RandArgs = 2; 51 | MATHVM_API bool Round(MATHVM_ARGS); constexpr int32 RoundArgs = 1; 52 | MATHVM_API bool RoundEven(MATHVM_ARGS); constexpr int32 RoundEvenArgs = 1; 53 | MATHVM_API bool Sign(MATHVM_ARGS); constexpr int32 SignArgs = 1; 54 | MATHVM_API bool Sin(MATHVM_ARGS); constexpr int32 SinArgs = 1; 55 | MATHVM_API bool Sqrt(MATHVM_ARGS); constexpr int32 SqrtArgs = 1; 56 | MATHVM_API bool Tan(MATHVM_ARGS); constexpr int32 TanArgs = 1; 57 | MATHVM_API bool Trunc(MATHVM_ARGS); constexpr int32 TruncArgs = 1; 58 | 59 | // Resources 60 | MATHVM_API bool Read(MATHVM_ARGS); constexpr int32 ReadArgs = -1; 61 | MATHVM_API bool Write(MATHVM_ARGS); constexpr int32 WriteArgs = -1; 62 | } 63 | } -------------------------------------------------------------------------------- /Source/MathVM/Public/MathVMResourceObject.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "UObject/NoExportTypes.h" 7 | #include "MathVMResources.h" 8 | #include "MathVMResourceObject.generated.h" 9 | 10 | /** 11 | * 12 | */ 13 | UCLASS(BlueprintType) 14 | class MATHVM_API UMathVMResourceObject : public UObject 15 | { 16 | GENERATED_BODY() 17 | 18 | public: 19 | void SetMathVMResource(TSharedPtr InMathVMResource); 20 | 21 | TSharedPtr GetMathVMResource() const; 22 | 23 | UFUNCTION(BlueprintCallable, BlueprintPure, Category = "MathVM") 24 | double Read(const TArray& Args); 25 | 26 | UFUNCTION(BlueprintCallable, Category = "MathVM") 27 | void Write(const TArray& Args); 28 | 29 | protected: 30 | TSharedPtr MathVMResource = nullptr; 31 | }; 32 | -------------------------------------------------------------------------------- /Source/MathVM/Public/MathVMResources.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roberto De Ioris. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Curves/CurveBase.h" 7 | #include "Engine/DataTable.h" 8 | #include "Engine/Texture2D.h" 9 | #include "MathVM.h" 10 | 11 | class MATHVM_API FMathVMTexture2DResource : public IMathVMResource 12 | { 13 | public: 14 | FMathVMTexture2DResource(UTexture2D* Texture); 15 | virtual double Read(const TArray& Args) const override; 16 | virtual void Write(const TArray& Args) override; 17 | 18 | protected: 19 | TArray Pixels; 20 | int32 Width = 0; 21 | int32 Height = 0; 22 | EPixelFormat PixelFormat = EPixelFormat::PF_Unknown; 23 | }; 24 | 25 | class MATHVM_API FMathVMCurveBaseResource : public IMathVMResource 26 | { 27 | public: 28 | FMathVMCurveBaseResource(UCurveBase* Curve); 29 | virtual double Read(const TArray& Args) const override; 30 | virtual void Write(const TArray& Args) override; 31 | 32 | protected: 33 | TArray Curves; 34 | }; 35 | 36 | class MATHVM_API FMathVMDoubleArrayResource : public IMathVMResource 37 | { 38 | public: 39 | FMathVMDoubleArrayResource(const int32 ArraySize); 40 | virtual double Read(const TArray& Args) const override; 41 | virtual void Write(const TArray& Args) override; 42 | 43 | protected: 44 | TArray Data; 45 | }; 46 | 47 | class MATHVM_API FMathVMDataTableResource : public IMathVMResource 48 | { 49 | public: 50 | FMathVMDataTableResource(UDataTable* DataTable, const TArray& FieldNames); 51 | virtual double Read(const TArray& Args) const override; 52 | virtual void Write(const TArray& Args) override; 53 | 54 | protected: 55 | TArray> Data; 56 | }; --------------------------------------------------------------------------------