Unknown mess
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
{
|
||||
"permissions": {
|
||||
"defaultMode": "acceptEdits",
|
||||
"allow": [
|
||||
"WebFetch(domain:dev.epicgames.com)",
|
||||
"WebFetch(domain:github.com)",
|
||||
"Bash(grep:*)"
|
||||
],
|
||||
"deny": [
|
||||
"Bash(git commit *)",
|
||||
"Bash(git push *)",
|
||||
"Bash(rm -rf *)"
|
||||
]
|
||||
],
|
||||
"defaultMode": "acceptEdits"
|
||||
}
|
||||
}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -46,3 +46,4 @@ luprex/ext/eris-master/src/luac
|
||||
luprex/ext/eris-master/test/persist
|
||||
luprex/ext/eris-master/test/unpersist
|
||||
|
||||
GPF-output/**
|
||||
|
||||
31
CLAUDE.md
31
CLAUDE.md
@@ -86,19 +86,28 @@ Blueprints call into Lua via two mechanisms:
|
||||
|
||||
Look-at widgets, hotkeys, and menus are built on top of this. The menu system is implemented entirely in "user space" Lua and blueprint code. See `Docs/Displaying-Widget-Blueprints.md`.
|
||||
|
||||
## Blueprint Text Export
|
||||
|
||||
Blueprints are automatically exported to readable text files in `Saved/BlueprintExports/` whenever they are saved in the editor. This lets Claude Code read blueprint logic. See `Docs/Blueprint Text Export.md` for format details. Source: `Source/Integration/BlueprintExporter.h/.cpp` and `Source/Integration/Integration.cpp`.
|
||||
|
||||
## Key Documentation
|
||||
|
||||
- `Docs/Predictive-Reexecution.md` — how the four world models stay in sync
|
||||
- `Docs/The-Event-Driven-Structure-of-the-Engine.md` — driver/driven separation, determinism, replay
|
||||
- `Docs/Multipass-Difference-Transmission.md` — the algorithm for syncing Lua table graphs
|
||||
- `Docs/Animation-Queues-and-Tangible-Actors.md` — how blueprints interpret animation queues
|
||||
- `Docs/Our-In-House-Lua-API.md` — the LuaStack API (LuaDefStack, LuaExtStack)
|
||||
- `Docs/A-Summary-of-our-Lua-Patches.md` — all modifications to the Lua runtime
|
||||
- `Docs/Major-Data-Structures.md` — World, tangibles, threads, classes, source database
|
||||
- `Docs/Displaying-Widget-Blueprints.md` — GUI system (invokes, probes, look-at widgets, menus)
|
||||
- `Docs/Global-Variables.md` — different types of global data and their transmission rules
|
||||
- `Docs/Correct-Implementation-of-Blocking-Operations-and-NoPredict.md` — how to handle blocking ops
|
||||
- `Docs/Difference-Transmission-with-Threads.md` — why concurrent diff transmission is hard
|
||||
Look in the Docs directory for important documentation.
|
||||
|
||||
## Git
|
||||
|
||||
Do not use git to make changes (commit, push, branch, etc.). Read-only git commands (status, log, diff, etc.) are fine.
|
||||
|
||||
## Workflow
|
||||
|
||||
- When the user gives a direct command, execute it. But when proposing changes on your own initiative, describe the plan and get approval before editing files.
|
||||
|
||||
## Coding Conventions
|
||||
|
||||
- Prefer early returns and `continue` to reduce nesting (never-nester style).
|
||||
- Do not use static functions in Unreal code. Use class methods or namespace-scoped functions instead.
|
||||
- Use `LogLuprexIntegration` for log messages, not `LogTemp`.
|
||||
- When writing UFUNCTIONs that take an `AActor*`, `UObject*`, or similar "self" parameter, add `DefaultToSelf` meta to that pin. Most functions should have this on the obvious pin so the user doesn't have to manually wire it in blueprints.
|
||||
|
||||
## Session Startup
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[/Script/Engine.Engine]
|
||||
+ActiveClassRedirects=(OldClassName="/Script/Integration.lxLookAtWidget",NewClassName="/Script/Integration.lxLuaWidget")
|
||||
+ActiveClassRedirects=(OldClassName="/Script/Integration.K2Node_FormatErrorMessage",NewClassName="/Script/Integration.K2Node_FormatLogMessage")
|
||||
|
||||
|
||||
[/Script/Engine.Engine]
|
||||
@@ -88,6 +89,9 @@ UIScaleCurve=(EditorCurveData=(Keys=((Time=480.000000,Value=0.444000),(Time=720.
|
||||
+CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn")
|
||||
+CollisionChannelRedirects=(OldName="Clickable",NewName="LookAtDetection")
|
||||
|
||||
[CoreRedirects]
|
||||
+EnumRedirects=(OldName="/Script/Integration.ElxLogVerbosity",NewName="/Script/Integration.ElxFormatLogVerbosity")
|
||||
|
||||
[/Script/GameplayDebugger.GameplayDebuggerConfig]
|
||||
ActivationKey=None
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -61,6 +61,11 @@ This patch adds the lua function *genlt* and the C function *lua_genlt*. This is
|
||||
|
||||
This patch is live and functioning. The generalized less-than operator is quite useful as the second parameter to lua's builtin *table.sort* function. We have also provided an iterator *table.sortedpairs* that is similar to the lua builtin *table.pairs* that iterates over a table in sorted order. This implicitly uses the *genlt* comparison operator.
|
||||
|
||||
We originally designed this patch to help with determinism.
|
||||
But we eventually realized it was the wrong design, and we
|
||||
ended up not needing it for determinism. It's still a
|
||||
very useful function, though.
|
||||
|
||||
## The Table Iterator Patch
|
||||
|
||||
This patch is designed to address the nondeterminism of the lua 'next' iterator. In the original Lua design, table iteration was nondeterministic. By that, I mean that in the original lua, I can create two empty tables, I can then perform the same sequence of insertions and deletions on those two tables. The two tables are identical: they have the same keys, and they've had the same same sequence of operations applied to them. But I can iterate over them using "table.pairs", and they produce their keys in two different orders. That's nondeterminism.
|
||||
@@ -81,23 +86,63 @@ This patch is live and is used implicitly whenever you iterate over a lua table.
|
||||
|
||||
## The Table Length Patch
|
||||
|
||||
The builtin lua function lua_len is nondeterministic. By that, I mean that two tables with the exact same keys might return different values for lua_len. We can't allow nondeterministic anything in our version of Lua. We have altered the implementation of lua_len so that it is deterministic. Two tables with the same keys will always return the same lua_len, that is now guaranteed.
|
||||
I've changed the lua length operator so that when it is
|
||||
applied to a table, it returns the number of keys in the
|
||||
table. It does this in constant time. This change affects
|
||||
lua_len, lua_rawlen, and the lua # operator.
|
||||
|
||||
Our new implementation of lua_len conforms to the specification in the documentation. I'm not sure that's the right thing to do.
|
||||
You might be wondering what the lua length operator
|
||||
used to do? The lua documentation says this:
|
||||
|
||||
It's obvious how this specification got written: they implemented an algorithm to find the length of a vector as efficiently as possible. By "vector," I mean a table whose keys are 1,2,3,4,5 and so forth. After they wrote this vector-length algorithm, somebody asked, "what happens if you apply that algorithm to a table that's not a vector?" The implementor replied, "it wasn't meant for non-vectors." "Ok, but what *does* it do if you apply it to a non-vector?" They puzzled it out, and they wrote down what it does as "the specification." But that specification when applied to non-vectors isn't *useful*. It's just what their vector-length algorithm happens to spit out when you feed it an input that it wasn't designed to handle.
|
||||
> The length operator applied on a table returns a border in
|
||||
> that table. A border in a table t is any non-negative
|
||||
> integer that satisfies the following condition:
|
||||
>
|
||||
> (border == 0 or t[border] ~= nil) and
|
||||
> (t[border + 1] == nil or border == math.maxinteger)
|
||||
|
||||
So why did they use an algorithm that only works on vectors? Why not use a better algorithm, one that can return the number of keys in the table regardless of whether the table is a vector? The answer is that given the lua table internal representation, returning the number of keys in the table is O(N), whereas the vector-only implementation is usually O(1).
|
||||
Those are *terrible* semantics:
|
||||
|
||||
However, I had to change the table internal representation for the table iterator patch (above). With the modified table representation, returning the number of keys in the table can be done in constant time, whether it's a vector or not.
|
||||
- They're not useful for anything.
|
||||
- It's not deterministic.
|
||||
- In no sense of the word "length" is this the
|
||||
length of the table.
|
||||
|
||||
Now, I'm seriously tempted to have lua_len just return the number of keys in the table. That would be so straightforward and self-explanatory, and faster than the current algorithm. The only reason I haven't done this is that it wouldn't conform to the specification! My new lua_len algorithm is similar to the original algorithm, in that it fails in exactly the same way on non-vectors, in order to be compliant with the specification.
|
||||
Let me explain how that mess happened. They obviously
|
||||
wanted the length operator to return the number of keys in
|
||||
the table. Unfortunately, to count the number of keys in a
|
||||
lua table actually takes O(N) time. So they came up with a
|
||||
hack to make it faster: O(1). Unfortunately, the hack relies
|
||||
on the table being a vector. That is, the table must have
|
||||
numbered keys starting with 1. As long as you apply their
|
||||
hack to a vector, it works perfectly and returns the
|
||||
number of keys.
|
||||
|
||||
Since this feels insane, I have also provided a totally new API function: lua_nkeys. This returns the number of keys in the table, full stop. It's constant-time.
|
||||
Unfortunately, if you apply the hacked length algorithm to a
|
||||
table that isn't a vector, it doesn't work at all.
|
||||
|
||||
This patch also includes a function lua_nthkey, to get the Nth item in the table iteration, random-access style. I am not certain that this is a good idea, and I have deliberately avoided the use of this function for now, until I am convinced that it's wise.
|
||||
But I think the lua documentation didn't want to admit, "it
|
||||
doesn't work at all." So instead, they invented this
|
||||
concept of "a border" and pretended that was in some way a
|
||||
helpful result. They should have just said, "the result is
|
||||
undefined."
|
||||
|
||||
This patch is live, and is necessary to the determinism of the system.
|
||||
I had to change the table internal representation
|
||||
for the table iterator patch (above). With the modified
|
||||
table representation, returning the number of keys in the
|
||||
table can be done in constant time, whether it's a vector or
|
||||
not. So I changed the length operator to just return
|
||||
the number of keys, full stop.
|
||||
|
||||
I've also added another function, lua_nkeys. This also
|
||||
returns the number of keys in the table. It doesn't add any
|
||||
functionality - I could use lua_rawlen and that would work
|
||||
just as well. However, using lua_nkeys emphasizes the fact
|
||||
that my code needs the *real* table length, not the "border"
|
||||
bullshit that lua used to provide.
|
||||
|
||||
This patch is live, and is necessary to the determinism of
|
||||
the system.
|
||||
|
||||
## The Table Flag Bits Patch
|
||||
|
||||
@@ -170,7 +215,7 @@ That's not bad, but it puts both values and methods into the same namespace:
|
||||
|
||||
- By putting both values and methods into the same namespace, we create the possibility of unintended mistakes.
|
||||
|
||||
I am thinking about implementing a new metatable entry: __METHODS = true. If this flag is present, then the colon operator *obj:method* looks for the method in the metatable, instead of looking for it in the object. With this new metamethod, the way to create a class would be to make a table full of methods, and then in the class table, put __METHODS = true. Then you would do this:
|
||||
I am thinking about implementing a new metatable entry: __METHODS = true. If this flag is present, then the colon operator *obj:method* looks for the method in the metatable, instead of looking for it in the object. With this new metamethod, the way to create a class would be to make a table full of methods, and then in that table, also put __METHODS = true. Then you would do this:
|
||||
|
||||
```lua
|
||||
setmetatable(obj, class)
|
||||
@@ -200,6 +245,6 @@ There's no obvious approach to fixing this, so I haven't patched it yet.
|
||||
|
||||
GC Finalizers and weak tables both introduce nondeterminism into Lua execution. We can't allow that. It may be necessary to patch the lua interpreter to simply disable these functions. Alternately, we could simply ask the scripters not to use these features, and declare "undefined behavior" if they do.
|
||||
|
||||
Update 1: I'm using GC finalizers in some cases to clean up userdata objects. I think it's safe as long as the only thing the finalizer does is free memory.
|
||||
Update 1: I'm using GC finalizers in some cases to clean up userdata objects. I think it's safe as long as the only thing the finalizer does is free memory. (NOTE: WHERE?)
|
||||
|
||||
Update 2: I don't remember using userdata objects at all. I am not sure that Update 1 is the truth any more.
|
||||
|
||||
@@ -1,58 +1,138 @@
|
||||
### About Determinism
|
||||
|
||||
The driven portion of the Luprex engine is deterministic. This document explains what that means and why it matters. For the specific rules you must follow to maintain determinism, see "The Event-Driven Structure of the Engine."
|
||||
Luprex uses two different kinds of determinism.
|
||||
|
||||
## Two Degrees of Determinism
|
||||
**Synchronous Model Determinism** Predictive reexecution
|
||||
uses four world models, including a server-synchronous and
|
||||
client-synchronous model. These two models are fed the same
|
||||
events, and must remain in the same state after executing
|
||||
the same events. See the document "Predictive Reexecution"
|
||||
for an explanation of why these models exist. I you were to
|
||||
do a comparison of the two models, they would be equal in
|
||||
the lisp sense of `equal`, but not in the sense of `eq`,
|
||||
because corresponding data structures are not at the
|
||||
same memory address.
|
||||
|
||||
There are two distinct degrees of determinism in the engine, each serving a different purpose.
|
||||
**Replay Log Determinism** The server stores a log of all
|
||||
events it feeds into the Luprex DLL. It can replay a log by
|
||||
feeding the same events into a new copy of the Luprex DLL.
|
||||
When replaying a log, the new copy of the Luprex DLL
|
||||
reproduces the original execution right down to the memory
|
||||
level: every data structure is at the same address, every
|
||||
byte of memory is the same. This is the `eq` level of
|
||||
equivalence.
|
||||
|
||||
**Value-level determinism** is the property that the server-synchronous and client-synchronous models stay in the same logical state. These two models run on different machines and receive the same command acknowledgements in the same order. Value-level determinism guarantees that they end up containing the same Lua tables with the same keys and values. If you go into both models and print things out, everything looks the same. However, a Lua table in one model is not necessarily at the same memory address as the corresponding table in the other, because they are running on different machines with different memory layouts. The two models are *equal* in the Lisp sense of `equal`, but not in the sense of `eq`.
|
||||
These two forms of determinism serve different purposes and
|
||||
impose different costs.
|
||||
|
||||
**Bit-exact determinism** is the property that a recorded event log, when replayed into a fresh DrivenEngine, reproduces the original execution right down to the memory level: every data structure is at the same address, every byte of memory is the same. This is the `eq` level of equivalence.
|
||||
## Implementing Synchronous Model Determinism
|
||||
|
||||
The engine currently aims for both, but they serve different purposes and impose different costs.
|
||||
To get the two synchronous models to be deterministic
|
||||
enough, we had to take several steps:
|
||||
|
||||
## Value-Level Determinism: Synchronous Model Pairing
|
||||
- **Deterministic Lua table iteration.** We patch the Lua
|
||||
runtime so that iterating over a table always produces
|
||||
keys in the same order. The order depends only on
|
||||
the order in which the keys were inserted, but not on the
|
||||
memory layout.
|
||||
- **No iterating over C++ unordered maps.** Unordered maps
|
||||
produce elements in an order that depends on memory
|
||||
addresses. Since addresses differ between the two models,
|
||||
iteration order would differ, breaking value-level
|
||||
determinism. An exception: iterating an unordered map and
|
||||
then immediately sorting the results into a predictable
|
||||
order is allowed, because the randomness is sandboxed.
|
||||
- **No genuinely random numbers.** We do not use random
|
||||
numbers in the world model. We do use pseudorandom
|
||||
numbers, we store the generator's state as part of the
|
||||
world model and maintain it using difference transmission.
|
||||
|
||||
Value-level determinism is what makes the multiplayer architecture work. It is non-negotiable.
|
||||
|
||||
Luprex uses four types of world models to handle multiplayer networking (see "Predictive Reexecution" for the full explanation). Two of these models are critical to understand here:
|
||||
|
||||
- The **server-synchronous** model runs on the server.
|
||||
- The **client-synchronous** model runs on the client.
|
||||
|
||||
These two models receive the same command acknowledgements in the same order. Because the driven portion is deterministic at the value level, the two models always end up in the same logical state: the same Lua tables, the same values, the same game state. They never need to exchange full state to stay in sync.
|
||||
|
||||
The two models are running on different machines, so naturally they have different memory layouts and different pointer addresses. That's fine. All that matters is that the values match. This is why value-level determinism is sufficient for synchronous model pairing.
|
||||
|
||||
The constraints that maintain value-level determinism are:
|
||||
|
||||
- **Deterministic Lua table iteration.** We patch the Lua runtime so that iterating over a table always produces keys in the same order, regardless of memory layout. Without this, two engines processing the same commands could iterate tables in different orders and produce different results.
|
||||
- **No iterating over unordered maps.** Unordered maps produce elements in an order that depends on memory addresses. Since addresses differ between the two models, iteration order would differ, breaking value-level determinism. (An exception: iterating an unordered map and then immediately sorting the results into a predictable order is allowed, because the randomness is sandboxed.)
|
||||
- **No genuinely random numbers.** Pseudorandom numbers are fine as long as the state is privately owned by the driven portion and seeded deterministically.
|
||||
- **Controlled use of real-time clocks.** The driven portion (the Luprex DLL) cannot call system functions to obtain the current time, because the result would differ between runs and between machines. However, the driver can feed the current time into the driven portion as an event. Since events are the same during paired execution and during replay, the time value is deterministic from the driven portion's perspective.
|
||||
|
||||
## Bit-Exact Determinism: Replay Debugging
|
||||
|
||||
Bit-exact determinism enables replay debugging. It is valuable but expensive, and its cost-benefit tradeoff is an open question.
|
||||
Bit-exact determinism enables replay debugging. It is
|
||||
valuable but expensive, and its cost-benefit tradeoff is an
|
||||
open question.
|
||||
|
||||
As the server runs, the driver can write a log of every event it feeds into the driven portion. Later, a new DrivenEngine can be created and fed those same events from the log file. The goal of bit-exact determinism is that during this replay, the DrivenEngine does the *exact* same thing it did during the live run, right down to every data structure being at the same memory address.
|
||||
As the server runs, the driver can write a log of every
|
||||
event it feeds into the driven portion. Later, a new
|
||||
DrivenEngine can be created and fed those same events from
|
||||
the log file. The goal of bit-exact determinism is that
|
||||
during this replay, the DrivenEngine does the *exact* same
|
||||
thing it did during the live run, right down to every data
|
||||
structure being at the same memory address.
|
||||
|
||||
Why does this matter? If the server crashed during the live run, the replay will crash in exactly the same way. You can run the replay inside a debugger, single-step right up to the crash, and examine the exact same pointers and memory layout that existed during the original crash.
|
||||
Why does this matter? If the server crashed during the live
|
||||
run, the replay will crash in exactly the same way. You can
|
||||
run the replay inside a debugger, single-step right up to
|
||||
the crash, and examine the exact same pointers and memory
|
||||
layout that existed during the original crash.
|
||||
|
||||
Value-level determinism alone is not sufficient for this. If the replay produces the same logical state but at different memory addresses, then pointer-related bugs (buffer overruns, use-after-free, etc.) might not reproduce. Bit-exact determinism ensures they do.
|
||||
Value-level determinism alone is not sufficient for this. If
|
||||
the replay produces the same logical state but at different
|
||||
memory addresses, then pointer-related bugs (buffer
|
||||
overruns, use-after-free, etc.) might not reproduce.
|
||||
Bit-exact determinism ensures they do.
|
||||
|
||||
The additional constraints that maintain bit-exact determinism, beyond those needed for value-level determinism, are:
|
||||
To implement replay determinism, we took several
|
||||
difficult steps:
|
||||
|
||||
- **The eng::malloc heap.** A custom memory allocator positioned at a fixed address, used exclusively by the driven portion. Because the driven portion is deterministic, the sequence of allocations and frees is identical between the live run and the replay, so every data structure ends up at the same address. See "The Event-Driven Structure of the Engine" for details.
|
||||
- **No threads in the driven portion.** Thread scheduling is nondeterministic at the OS level. Even if two threaded programs produce the same final values, the interleaving of operations differs between runs, which would cause memory allocations to occur in different orders and at different addresses.
|
||||
- **The Driver/Driven Partition**. The luprex engine is
|
||||
event-driven portion, and an event-driver. The driven
|
||||
portion contains all the game logic. The driver is mainly
|
||||
for I/O. The driven portion cannot contain any I/O. That
|
||||
includes:
|
||||
|
||||
Note that the constraints for value-level determinism (deterministic table iteration, no unordered maps, etc.) also contribute to bit-exact determinism. But they are *required* for value-level determinism regardless. The eng::malloc heap and the no-threads rule are the additional cost imposed specifically by the bit-exact guarantee.
|
||||
- **Clocks only in the Driver.** The driven portion cannot
|
||||
call system functions to obtain the current time.
|
||||
However, the driver can feed the current time into the
|
||||
driven portion as an event.
|
||||
- **Lua Source files only in the Driver** The driven
|
||||
portion cannot read lua source files. It can however
|
||||
enter a state that indicates to the driver that it
|
||||
wants a lua source file. Then, the driver can feed
|
||||
the lua source file in as an event.
|
||||
- **Sockets only in the Driver** The driven portion
|
||||
cannot open TCP/IP sockets. However, it can enter
|
||||
a state that indicates its desire to make a TCP/IP
|
||||
connection, and then the driver can do it and feed
|
||||
the data into the driven portion.
|
||||
|
||||
## The Practical Distinction
|
||||
- **The eng::malloc heap.** A custom memory allocator
|
||||
positioned at a fixed address, used exclusively by the
|
||||
driven portion. The memory allocator, if asked to
|
||||
perform the same sequence of malloc/free operations,
|
||||
will return the same addresses.
|
||||
|
||||
If the engine ever relaxed its determinism requirements, the value-level constraints would remain because they are essential to the multiplayer architecture. The bit-exact constraints (eng::malloc, no threads) could theoretically be dropped if replay debugging were deemed not worth the cost. Dropping the no-threads rule in particular would be a significant performance benefit.
|
||||
- **No threads in the driven portion.** Thread scheduling is
|
||||
nondeterministic at the OS level. We cannot use it in the
|
||||
driven portion.
|
||||
|
||||
## Should we Ditch Replay Determinism?
|
||||
|
||||
Implementing synchronous model determinism is necessary
|
||||
for predictive reexecution. It is non-negotiable.
|
||||
|
||||
On the other hand, replay log determinism is not necessarily
|
||||
required for us to have a usable engine. We could ditch it.
|
||||
It certainly does impose a lot of difficult constraints on
|
||||
the engine.
|
||||
|
||||
The driver/driven distinction certainly required us to tie
|
||||
ourselves into knots in some part of the engine design.
|
||||
But, that's pretty baked in at this point, we're probably
|
||||
never going to change that.
|
||||
|
||||
However, it also imposes a no-threads requirement. That
|
||||
is certainly a bummer from a performance perspective.
|
||||
|
||||
## Lua Scripters Don't Need to Worry
|
||||
|
||||
The Lua environment is carefully sandboxed to be deterministic at both levels without any effort from the scripter. Lua's random number generators are seeded pseudorandom generators owned by the driven portion. Table iteration is patched to be deterministic. Lua "threads" (coroutines) are not real OS threads and don't run concurrently. The scripter writes ordinary Lua code and gets determinism for free.
|
||||
The Lua environment is carefully sandboxed to be
|
||||
deterministic at both levels without any effort from the
|
||||
scripter. Lua's random number generators are seeded
|
||||
pseudorandom generators owned by the driven portion. Table
|
||||
iteration is patched to be deterministic. Lua "threads"
|
||||
(coroutines) are not real OS threads and don't run
|
||||
concurrently. The scripter writes ordinary Lua code and gets
|
||||
determinism for free.
|
||||
|
||||
@@ -272,9 +272,9 @@ It is perfectly safe to call FinishedAnimation from anywhere. You can call it fr
|
||||
|
||||
Some animations are instantaneous, such as "warpto." They take zero frames to complete. The following sequence of events occurs: the lua code pushes the "warpto" animation into the queue. The Unreal C++ code sees this and calls *AnimationQueueChanged*. This, in turn, calls *Init Action: Warpto*. That routine actually performs the warpto, and then calls *FinishedAnimation* immediately. So it's even fine to call *FinishedAnimation* directly from the routine that initiates an animation, if you want the animation to be finished as soon as it starts.
|
||||
|
||||
The routine FinishAnimation takes an *lxAnimationStep* as a parameter. This is so that it knows which animation step to mark as finished. TangibleStaticMesh always passes in the variable *CurrentAnimation*.
|
||||
The routine FinishedAnimation takes an *lxAnimationStep* as a parameter. This is so that it knows which animation step to mark as finished. TangibleStaticMesh always passes in the variable *CurrentAnimation*.
|
||||
|
||||
*FinishAnimation* also takes three boolean parameters: Auto Update XYZ, Auto Update Plane, and Auto Update Facing. These require some explanation. Suppose that the lua programmer pushes an animation step that looks like this:
|
||||
*FinishedAnimation* also takes three boolean parameters: Auto Update XYZ, Auto Update Plane, and Auto Update Facing. These require some explanation. Suppose that the lua programmer pushes an animation step that looks like this:
|
||||
|
||||
```lua
|
||||
tangible.animate{tan=actor, anim={action="emote", animation="dance", xyz={1,2,3}}}
|
||||
@@ -284,7 +284,7 @@ Since "play an emote" isn't a travel command like "moveto" or "warpto", the lua
|
||||
|
||||
The convention that we have adopted is that to recover from this type of mistake, it is considered acceptable to just play the emote in-place (ie, without moving the actor), then, when the emote is fully finished, the blueprint warps the player to the specified XYZ coordinate. In other words, every animation step is treated as if it has an *implicit* "warpto" at the end of it. This rule guarantees that if the lua programmer sets the xyz, facing, or plane in an animation step, the character will end up at the desired xyz, facing, and plane, no matter what the animation step is.
|
||||
|
||||
That's why *FinishAnimation* has those three boolean flags: Auto Update XYZ, Auto Update Plane, Auto Update Facing. If those are all true – and they almost always should be – then *FinishAnimation* will implement the implicit "warpto" for you. I cannot think of a situation where you would want these flags to be false, but I have left the option, in case somebody wants to do something odd in a Tangible Actor.
|
||||
That's why *FinishedAnimation* has those three boolean flags: Auto Update XYZ, Auto Update Plane, Auto Update Facing. If those are all true – and they almost always should be – then *FinishedAnimation* will implement the implicit "warpto" for you. I cannot think of a situation where you would want these flags to be false, but I have left the option, in case somebody wants to do something odd in a Tangible Actor.
|
||||
|
||||
## How Tangible Actors handle Warping Away
|
||||
|
||||
|
||||
49
Docs/Blueprint Text Export.md
Normal file
49
Docs/Blueprint Text Export.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Blueprint Text Export
|
||||
|
||||
Blueprints are stored as binary `.uasset` files that Claude Code cannot read directly. To work around this, a text exporter automatically converts blueprint graphs to readable text files whenever a blueprint is saved in the Unreal editor.
|
||||
|
||||
## How It Works
|
||||
|
||||
The game module (`FlxIntegrationModuleImpl` in `Source/Integration/Integration.cpp`) hooks into `UPackage::PackageSavedWithContextEvent`. When a blueprint is saved, it iterates each `UEdGraph` in the blueprint and runs `FlxBlueprintExporter` on it. The output is written to `Saved/BlueprintExports/<BlueprintName>/<GraphName>.txt`.
|
||||
|
||||
The exporter class (`Source/Integration/BlueprintExporter.h/.cpp`) processes one graph at a time. The constructor runs all passes and the result is available via `GetOutput()`.
|
||||
|
||||
## Output Format
|
||||
|
||||
The graph file is written to `Saved/BlueprintExports/<BlueprintName>/<GraphName>.txt`. A details file with node-name-to-GUID mappings is written to `Saved/BlueprintExports/<BlueprintName>/DETAILS/<GraphName>.txt`.
|
||||
|
||||
Every line in the graph file starts with a keyword, making it easy to parse. The format is:
|
||||
|
||||
```
|
||||
node Event_Tick
|
||||
return Output_Delegate, Delta_Seconds
|
||||
goto Set_Tick_Delta_Seconds
|
||||
|
||||
node Set_Tick_Delta_Seconds
|
||||
input Real Tick_Delta_Seconds = Event_Tick.Delta_Seconds
|
||||
return Output_Get
|
||||
goto CallFunctionByName
|
||||
```
|
||||
|
||||
### Line Keywords
|
||||
|
||||
- `node Name` — starts a new node block.
|
||||
- `input Type Name = Source` — an input data pin. Source is a `Node.Pin` reference, a literal value, `<self>`, or `<default>`.
|
||||
- `return Pin1, Pin2` — output data pins.
|
||||
- `goto Target` — exec flow (single output).
|
||||
- `goto-if PinName Target` — exec flow (multiple outputs, e.g. branch true/false).
|
||||
- `// comment text` — comment node.
|
||||
|
||||
### Special Handling
|
||||
|
||||
- String defaults are shown in quotes.
|
||||
- Variable get nodes are inlined (the variable name appears directly at the point of use rather than as a separate node).
|
||||
- Knot (reroute) nodes are followed through transparently.
|
||||
|
||||
## Node Ordering
|
||||
|
||||
Nodes are sorted by a traversal algorithm: find starter nodes (exec output but no exec input), sort them by Y position, then traverse each. The traversal visits a node's inputs first (so data sources appear before their consumers), then emits the node itself, then follows exec outputs. This produces a readable top-down flow. Any unvisited nodes are appended at the end.
|
||||
|
||||
## Node Naming
|
||||
|
||||
Names are derived from `ENodeTitleType::ListView` titles, sanitized to alphanumeric plus underscores. Duplicates get `_2`, `_3`, etc.
|
||||
57
Docs/Comment-Formatting-Standards.md
Normal file
57
Docs/Comment-Formatting-Standards.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Comment Formatting Standards
|
||||
|
||||
## Line Width
|
||||
|
||||
Wrap comments at approximately 60 columns. Code and function signatures are not wrapped.
|
||||
|
||||
## Comment Style
|
||||
|
||||
Use C++ style (`//`) for all comments. Use `/** */` only for UENUM/UPROPERTY values that should appear as tooltips in the blueprint editor.
|
||||
|
||||
## Section Banners
|
||||
|
||||
Each class, struct, or enum gets a banner block. The banner line is 60 characters of slashes. The banner names the type and gives a brief description.
|
||||
|
||||
```cpp
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FlxExampleClass
|
||||
//
|
||||
// Brief description of what this class does.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
```
|
||||
|
||||
## Header File Banners
|
||||
|
||||
Ideally, we'd like to have documentation about what a header
|
||||
file is for. This would be a banner block at the top of the
|
||||
header file, before the pragma once. If you're claude code,
|
||||
and there is no documentation yet, generate a little, but
|
||||
mark it "DOCUMENTATION GENERATED BY CLAUDE."
|
||||
|
||||
## Member Comments
|
||||
|
||||
Comments on member variables and functions use `//` followed by a blank `//` line:
|
||||
|
||||
```cpp
|
||||
// Brief description of what this does.
|
||||
//
|
||||
void DoSomething();
|
||||
```
|
||||
|
||||
## UFUNCTION Spacing
|
||||
|
||||
UFUNCTION-decorated members should be followed by a single blank line to visually separate them from the next member:
|
||||
|
||||
```cpp
|
||||
// Get the value.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable)
|
||||
int32 GetValue() const;
|
||||
|
||||
// Set the value.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void SetValue(int32 Val);
|
||||
```
|
||||
@@ -88,14 +88,22 @@
|
||||
},
|
||||
"problemMatcher": [
|
||||
{
|
||||
"base": "$gcc",
|
||||
"owner": "build-luprex",
|
||||
"source": "build.py",
|
||||
"fileLocation": ["relative", "${workspaceFolder}/luprex"]
|
||||
"fileLocation": ["relative", "${workspaceFolder}/luprex"],
|
||||
"pattern": {
|
||||
"regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
|
||||
"file": 1, "line": 2, "column": 3, "severity": 4, "message": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"base": "$gcc",
|
||||
"owner": "build-integration",
|
||||
"source": "build.py",
|
||||
"fileLocation": ["relative", "${workspaceFolder}"]
|
||||
"fileLocation": ["relative", "${workspaceFolder}"],
|
||||
"pattern": {
|
||||
"regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
|
||||
"file": 1, "line": 2, "column": 3, "severity": 4, "message": 5
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "shell"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
#include "Common.h"
|
||||
#include "AnimQueue.h"
|
||||
#include "Common.h"
|
||||
#include "UtilityLibrary.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Components/MeshComponent.h"
|
||||
|
||||
@@ -8,17 +8,17 @@
|
||||
|
||||
#include "AnimQueue.generated.h"
|
||||
|
||||
////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// An single animation step.
|
||||
// FlxAnimationStep
|
||||
//
|
||||
// This struct contains an entire animation step. The
|
||||
// key-value pairs are stored in an encoded form, which is not
|
||||
// directly accessible to blueprints. To read these key-value
|
||||
// pairs, blueprints will need to use UnpackAnimationStep or
|
||||
// AnimationStepGetXXX.
|
||||
// A single animation step. The key-value pairs
|
||||
// are stored in an encoded form, which is not
|
||||
// directly accessible to blueprints. To read
|
||||
// them, use UnpackAnimationStep or the
|
||||
// AnimationStepGetXXX functions.
|
||||
//
|
||||
////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct INTEGRATION_API FlxAnimationStep {
|
||||
@@ -28,14 +28,16 @@ public:
|
||||
UPROPERTY()
|
||||
bool Finished;
|
||||
|
||||
// The hash of the animation step, a 63-bit unique identifier.
|
||||
// The hash of the animation step, a 63-bit
|
||||
// unique identifier.
|
||||
//
|
||||
UPROPERTY()
|
||||
int64 Hash;
|
||||
|
||||
// The Body contains all the key-value pairs in an encoded form. To
|
||||
// obtain these in a useful form, you will need to use
|
||||
// UnpackAnimationStep or AnimationStepGetXXX.
|
||||
// The Body contains all the key-value pairs
|
||||
// in an encoded form. To obtain these in a
|
||||
// useful form, use UnpackAnimationStep or the
|
||||
// AnimationStepGetXXX functions.
|
||||
//
|
||||
UPROPERTY()
|
||||
TArray<uint8> Body;
|
||||
@@ -48,25 +50,26 @@ public:
|
||||
|
||||
// Auto-Execute
|
||||
//
|
||||
// These functions automatically update certain actor
|
||||
// properties:
|
||||
// These functions automatically update certain
|
||||
// actor properties:
|
||||
//
|
||||
// AutoUpdateXYZ(AActor *actor); // uses 'xyz' keyword
|
||||
// AutoUpdateFacing(AActor *actor); // uses 'facing' keyword.
|
||||
// AutoUpdatePlane(FName *plane); // uses 'plane' keyword
|
||||
// AutoUpdateXYZ - uses 'xyz' keyword
|
||||
// AutoUpdateFacing - uses 'facing' keyword
|
||||
// AutoUpdatePlane - uses 'plane' keyword
|
||||
//
|
||||
void AutoUpdateXYZ(AActor *actor) const;
|
||||
void AutoUpdateFacing(AActor *actor) const;
|
||||
void AutoUpdatePlane(FName *plane) const;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This UClass is never instantiated. It exists to
|
||||
// expose certain static functions to the blueprint
|
||||
// library.
|
||||
// UlxAnimationStepLibrary
|
||||
//
|
||||
////////////////////////////////////////////////
|
||||
// Blueprint function library for reading and
|
||||
// applying animation steps.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
UCLASS()
|
||||
class INTEGRATION_API UlxAnimationStepLibrary : public UBlueprintFunctionLibrary
|
||||
@@ -77,26 +80,23 @@ public:
|
||||
UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step")
|
||||
static FString AnimationStepDebugString(const FlxAnimationStep& step);
|
||||
|
||||
// Stores the key-value pairs in properties of the target object.
|
||||
// Stores the key-value pairs in properties of the
|
||||
// target object.
|
||||
//
|
||||
// The animation step contains key-value pairs. The goal of this function
|
||||
// is to store these in properties of the target. The prefix parameter
|
||||
// is prepended to the property names.
|
||||
// The prefix parameter is prepended to the property
|
||||
// names. For example, if the pairs are "color=blue,
|
||||
// speed=20" and the prefix is "aq", then "aq Color" is
|
||||
// set to "blue" and "aq Speed" is set to 20. A
|
||||
// property "aq Animation Step" is also written,
|
||||
// containing the entire animation step.
|
||||
//
|
||||
// For example, suppose the key-value pairs are "color=blue, speed=20",
|
||||
// and suppose the prefix is "aq". In that case, two properties would
|
||||
// be written: "aq Color" would be set to "blue", and "aq Speed" would be
|
||||
// set to 20. In addition, a property "aq Animation Step" would
|
||||
// be written, containing the entire animation step.
|
||||
// If a key has no corresponding property, it is
|
||||
// silently ignored. If a property has no corresponding
|
||||
// key, it is cleared.
|
||||
//
|
||||
// If the step contains a key-value pair, but there is no corresponding
|
||||
// property in the target, then the key-value pair is silently
|
||||
// ignored. If the target contains properties that begin with the prefix,
|
||||
// but which don't have any corresponding key-value pair, those properties
|
||||
// are cleared.
|
||||
//
|
||||
// Returns 'Changed' if the hash-value of the "aq Animation Step" property
|
||||
// has changed. Returns 'Action' string from the action=xxx property.
|
||||
// Returns bChanged=true if the hash of the "aq
|
||||
// Animation Step" property has changed. Returns the
|
||||
// Action string from action=xxx.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = "Luprex|Animation Step")
|
||||
static void UnpackAnimationStep(bool &bChanged, FString &Action, const FlxAnimationStep& step, UObject* target, const FString& VariableNamePrefix = TEXT("aq"));
|
||||
@@ -125,29 +125,55 @@ public:
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintAutocast), Category = "Luprex|Animation Step")
|
||||
static int64 AnimationStepID(const FlxAnimationStep& step) { return step.Hash; }
|
||||
|
||||
<<<<<<< HEAD
|
||||
// Using mat_xxxx values from the animation step, update the actor's
|
||||
// material parameters. Doing this may involve creating or replacing
|
||||
// dynamic material instances for the actor.
|
||||
=======
|
||||
// Scan an animation step for key-value pairs of the
|
||||
// form mat_XXXX={x,y,z}. For each match, create a
|
||||
// dynamic material instance on the actor's mesh
|
||||
// components and set the vector parameter XXXX.
|
||||
// Materials are restored to their base (non-dynamic)
|
||||
// state before applying, so parameters from previous
|
||||
// calls do not persist.
|
||||
>>>>>>> 9b1dd00a45a7b17c3546f8574d00e5ec78f17c75
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "actor"), Category = "Luprex|Animation Step")
|
||||
static void AnimationStepApplyMaterials(const FlxAnimationStep& step, AActor* actor);
|
||||
|
||||
<<<<<<< HEAD
|
||||
// Look for a mesh=name key-value pair in the animation step.
|
||||
// If found, load the named mesh and apply it to the actor's
|
||||
// mesh component. The actor must have exactly one mesh component.
|
||||
// If FallbackToBP is true, and mesh=name is not present, looks
|
||||
// for a bp=name pair instead.
|
||||
=======
|
||||
// Look for a mesh=name key-value pair. If found, load
|
||||
// the named mesh and apply it to the actor's mesh
|
||||
// component. The actor must have exactly one mesh
|
||||
// component.
|
||||
>>>>>>> 9b1dd00a45a7b17c3546f8574d00e5ec78f17c75
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "actor"), Category = "Luprex|Animation Step")
|
||||
static void AnimationStepApplyMesh(const FlxAnimationStep& step, bool FallbackToBP, AActor* actor);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
<<<<<<< HEAD
|
||||
// An animation step that doesn't actually store the step,
|
||||
// it just contains a pointer to the string.
|
||||
//
|
||||
////////////////////////////////////////////////
|
||||
=======
|
||||
// FlxAnimationStepView
|
||||
//
|
||||
// A lightweight, non-owning view of an animation
|
||||
// step (hash + body as a string_view).
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
>>>>>>> 9b1dd00a45a7b17c3546f8574d00e5ec78f17c75
|
||||
|
||||
struct FlxAnimationStepView {
|
||||
int64 Hash;
|
||||
@@ -157,19 +183,17 @@ struct FlxAnimationStepView {
|
||||
FlxAnimationStepView(int64 h, std::string_view b) : Hash(h), Body(b) {}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// A single animation field.
|
||||
// FlxAnimationField
|
||||
//
|
||||
// A field consists of a variable name,
|
||||
// a persistent flag, a type, and some fields
|
||||
// to hold the values.
|
||||
// A single field from an animation step: a variable name, a
|
||||
// persistent flag, a type, and value storage.
|
||||
//
|
||||
// If the value is boolean, it is stored in
|
||||
// X, as either 1 or 0. If the value is double,
|
||||
// it is stored in X.
|
||||
// Boolean values are stored in X as 1 or 0. Double values
|
||||
// are stored in X.
|
||||
//
|
||||
////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
struct FlxAnimationField {
|
||||
std::string_view Name;
|
||||
@@ -179,28 +203,27 @@ struct FlxAnimationField {
|
||||
std::string_view S;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// An Animation Queue Decoder.
|
||||
// FlxAnimQueueDecoder
|
||||
//
|
||||
// This acts a lot like a stream reader,
|
||||
// it reads one AnimEntry at a time from
|
||||
// the animation queue until you reach
|
||||
// 'end-of-file'.
|
||||
// Stream reader for animation queues. Reads one
|
||||
// FlxAnimationStepView at a time until EOF.
|
||||
//
|
||||
////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
class FlxAnimQueueDecoder {
|
||||
private:
|
||||
FlxStreamBuffer Decoder;
|
||||
|
||||
// These values are immediately read from the header.
|
||||
// Read from the header immediately on
|
||||
// construction.
|
||||
//
|
||||
int SizeLimit;
|
||||
int ActualSize;
|
||||
|
||||
public:
|
||||
// Initialize the FlxAnimQueueDecoder with the encoded animation queue.
|
||||
// Initialize with an encoded animation queue.
|
||||
//
|
||||
FlxAnimQueueDecoder(std::string_view s);
|
||||
|
||||
@@ -208,11 +231,11 @@ public:
|
||||
//
|
||||
int GetSizeLimit() const { return SizeLimit; }
|
||||
|
||||
// Get the Actual Size of the animation queue.
|
||||
// Get the actual size of the animation queue.
|
||||
//
|
||||
int GetActualSize() const { return ActualSize; }
|
||||
|
||||
// Return true if the parser has reached the end of the string.
|
||||
// Return true if the parser has reached EOF.
|
||||
//
|
||||
bool AtEOF() { return Decoder.empty(); }
|
||||
|
||||
@@ -220,7 +243,7 @@ public:
|
||||
//
|
||||
FlxAnimationStepView ReadStep();
|
||||
|
||||
// Peek at the hash of the next animation step.
|
||||
// Peek at the hash of the next step.
|
||||
//
|
||||
int64 PeekHash();
|
||||
|
||||
@@ -229,27 +252,30 @@ public:
|
||||
// static FString DebugString(std::string_view s);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// An Animation Step Decoder.
|
||||
// FlxAnimationStepDecoder
|
||||
//
|
||||
// This acts a lot like a stream reader,
|
||||
// it reads one FlxAnimationField at a time from
|
||||
// the animation queue until you reach
|
||||
// 'end-of-file'.
|
||||
// Stream reader for a single animation step. Reads one
|
||||
// FlxAnimationField at a time until EOF.
|
||||
//
|
||||
////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
class FlxAnimationStepDecoder {
|
||||
private:
|
||||
FlxStreamBuffer Decoder;
|
||||
|
||||
public:
|
||||
<<<<<<< HEAD
|
||||
// Initialize the FlxAnimationStepDecoder.
|
||||
//
|
||||
=======
|
||||
// Initialize from an encoded step body.
|
||||
//
|
||||
>>>>>>> 9b1dd00a45a7b17c3546f8574d00e5ec78f17c75
|
||||
FlxAnimationStepDecoder(std::string_view body) : Decoder(body) {}
|
||||
|
||||
// Return true if the parser has reached the end of the string.
|
||||
// Return true if the parser has reached EOF.
|
||||
//
|
||||
bool AtEOF() { return Decoder.empty(); }
|
||||
|
||||
@@ -257,28 +283,25 @@ public:
|
||||
//
|
||||
FlxAnimationField ReadField();
|
||||
|
||||
// Convert an AnimStep to an FString.
|
||||
// Convert an animation step to a debug string.
|
||||
//
|
||||
static FString DebugString(bool finished, int64 hash, std::string_view body);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FlxAnimTracker
|
||||
//
|
||||
// This class monitors the animation queue for a single
|
||||
// tangible. It can identify when a new animation has
|
||||
// appeared on the animation queue, and when animations have
|
||||
// been removed from the animation queue. It also
|
||||
// keeps track of which animations have been started.
|
||||
// Monitors the animation queue for a single tangible.
|
||||
// Identifies when animations appear or are removed, and
|
||||
// tracks which ones have been marked as finished.
|
||||
//
|
||||
////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
class FlxAnimTracker {
|
||||
public:
|
||||
// Our own copy of the animation queue. We only
|
||||
// store the hashes, not the steps. The First element
|
||||
// of the queue is the oldest item.
|
||||
// Our copy of the animation queue. The first element
|
||||
// is the oldest item.
|
||||
//
|
||||
TArray<FlxAnimationStep> AQ;
|
||||
|
||||
@@ -287,57 +310,48 @@ public:
|
||||
bool Changed;
|
||||
|
||||
public:
|
||||
// Construct a tracker.
|
||||
//
|
||||
// Initially, the tracker is in the empty (Clear) state.
|
||||
// Construct a tracker in the empty (Clear) state.
|
||||
//
|
||||
FlxAnimTracker();
|
||||
|
||||
// Clear everything, reset to the initial state.
|
||||
// Clear everything, reset to initial state.
|
||||
//
|
||||
void Clear();
|
||||
|
||||
// Update from the specified animation queue.
|
||||
//
|
||||
// After the update is done, AQ will be a copy
|
||||
// of the animation queue that is passed in.
|
||||
// Update from the specified animation queue. After the
|
||||
// update, AQ will be a copy of the queue that was
|
||||
// passed in.
|
||||
//
|
||||
void Update(std::string_view encqueue);
|
||||
|
||||
// Get the current blueprint name, as a string.
|
||||
// Get the current blueprint name.
|
||||
//
|
||||
FString GetCurrentBlueprintName();
|
||||
|
||||
// Get the current animation step.
|
||||
//
|
||||
// Get the current animation step. This is the step that the
|
||||
// blueprint should currently be playing.
|
||||
// Get the current animation step. This is the step
|
||||
// that the blueprint should currently be playing.
|
||||
//
|
||||
FlxAnimationStep GetCurrentAnimation();
|
||||
|
||||
// Declare that an animation is finished.
|
||||
//
|
||||
// The blueprint uses this function call to indicate that it
|
||||
// is done playing the specified animation. This will cause the
|
||||
// animation to be marked as finished, which in turn causes
|
||||
// 'GetCurrentStep' to advance to the next animation.
|
||||
// Declare that an animation is finished. The blueprint
|
||||
// calls this to indicate that it is done playing the
|
||||
// specified animation. This causes GetCurrentAnimation
|
||||
// to advance to the next step.
|
||||
//
|
||||
void FinishedAnimation(int64 Hash);
|
||||
|
||||
// Return true if an animation step is marked finished.
|
||||
//
|
||||
// Also return true if the animation step is not found.
|
||||
// Also returns true if the step is not found.
|
||||
//
|
||||
bool IsFinished(int64 Hash);
|
||||
|
||||
// Skip to the end of the animation queue.
|
||||
//
|
||||
// This is equivalent to calling 'FinishedHash' on every
|
||||
// animation in the entire queue.
|
||||
// Skip to the end of the animation queue. Equivalent to
|
||||
// calling FinishedAnimation on every animation in the
|
||||
// queue.
|
||||
//
|
||||
void SkipToEnd();
|
||||
|
||||
// Get all the hashes of all the animation steps.
|
||||
// Get the hashes of all animation steps.
|
||||
//
|
||||
TArray<int64> GetHashes();
|
||||
|
||||
@@ -349,24 +363,25 @@ public:
|
||||
//
|
||||
const FlxAnimationStep *LastFinished() const;
|
||||
|
||||
// Return the first animation with the specified hash.
|
||||
// Return the first animation with the
|
||||
// specified hash.
|
||||
//
|
||||
const FlxAnimationStep *FindAnimation(int64 hash) const;
|
||||
|
||||
// Clear the 'Changed' flag.
|
||||
// Clear the Changed flag.
|
||||
//
|
||||
void ClearChanged() { Changed = false; }
|
||||
|
||||
// Get the 'Changed' flag.
|
||||
// Get the Changed flag.
|
||||
//
|
||||
// The changed flag is set to true whenever the Luprex animation
|
||||
// queue changes from its previous state. The changed flag is also
|
||||
// set to true whenever 'SetFinished' marks an animation as finished.
|
||||
// The changed flag can only be set to false by 'ClearChanged,' above.
|
||||
// Set to true whenever the animation queue changes, or
|
||||
// when FinishedAnimation marks a step. Only cleared by
|
||||
// ClearChanged.
|
||||
//
|
||||
bool IsChanged() const { return Changed; }
|
||||
|
||||
// Return a debug string for the entire animation tracker.
|
||||
// Return a debug string for the entire animation
|
||||
// tracker.
|
||||
//
|
||||
FString DebugString() const;
|
||||
};
|
||||
@@ -1,3 +1,14 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// AssetLookup.h
|
||||
//
|
||||
// Provides asset loading by short name. At
|
||||
// initialization, scans asset directories and builds
|
||||
// a lookup table mapping (class, short name) pairs
|
||||
// to full asset paths. Blueprint-callable functions
|
||||
// let blueprints load assets by short name.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -14,20 +25,18 @@ class UStaticMesh;
|
||||
class USkeletalMesh;
|
||||
class UAnimSequence;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// UlxAssetLookup
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
UCLASS(MinimalAPI)
|
||||
class UlxAssetLookup : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
private:
|
||||
//
|
||||
// At initialization time, we scan the asset directories
|
||||
// to see what assets are present. These maps store the
|
||||
// result of the scan. Each entry in the map contains a
|
||||
// classname, a short name, and a path:
|
||||
//
|
||||
// <UAnimSequence, Jump> -> "/Game/AnimSequences/SEQ_Jump"
|
||||
//
|
||||
TMap<TTuple<FString, FName>, FString> AssetPaths;
|
||||
|
||||
private:
|
||||
@@ -39,37 +48,43 @@ private:
|
||||
public:
|
||||
void RebuildIndex();
|
||||
|
||||
// Get a static mesh by name
|
||||
// Get a static mesh by name.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading")
|
||||
static ElxValidOrNotValid LoadStaticMeshAsset(
|
||||
UStaticMesh *&Result,
|
||||
const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
|
||||
|
||||
// Get a skeletal mesh by name
|
||||
// Get a skeletal mesh by name.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading")
|
||||
static ElxValidOrNotValid LoadSkeletalMeshAsset(
|
||||
USkeletalMesh *&Result,
|
||||
const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
|
||||
|
||||
// Get an animation sequence by name.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading")
|
||||
static ElxValidOrNotValid LoadAnimSequenceAsset(
|
||||
UAnimSequence *&Result,
|
||||
const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
|
||||
|
||||
// Get a tangible class by name
|
||||
// Get a tangible class by name.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading")
|
||||
static ElxValidOrNotValid LoadTangibleBlueprintAsset(
|
||||
TSubclassOf<AActor> &Result,
|
||||
const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
|
||||
|
||||
// Get a widget blueprint by name
|
||||
// Get a widget blueprint by name.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading")
|
||||
static ElxValidOrNotValid LoadUserWidgetAsset(
|
||||
TSubclassOf<UUserWidget> &Result,
|
||||
const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
|
||||
|
||||
// Get a look-at widget blueprint by name
|
||||
// Get a look-at widget blueprint by name.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading")
|
||||
static ElxValidOrNotValid LoadLuaWidgetAsset(
|
||||
TSubclassOf<UlxLuaWidget> &Result,
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
|
||||
#include "BlueprintErrors.h"
|
||||
#include "Blueprint/BlueprintExceptionInfo.h"
|
||||
#include "LuaCall.h"
|
||||
#include "Internationalization/TextFormatter.h"
|
||||
#include "Kismet/KismetSystemLibrary.h"
|
||||
#include "Kismet2/KismetDebugUtilities.h"
|
||||
#include "Kismet/KismetTextLibrary.h"
|
||||
#include "AnimQueue.h"
|
||||
|
||||
ELogVerbosity::Type UlxBlueprintErrorLibrary::ConvertElxLogVerbosity(ElxLogVerbosity Verbosity) {
|
||||
switch (Verbosity) {
|
||||
case ElxLogVerbosity::Error: return ELogVerbosity::Error;
|
||||
case ElxLogVerbosity::Warning: return ELogVerbosity::Warning;
|
||||
case ElxLogVerbosity::Display: return ELogVerbosity::Display;
|
||||
case ElxLogVerbosity::Log: return ELogVerbosity::Log;
|
||||
case ElxLogVerbosity::Verbose: return ELogVerbosity::Verbose;
|
||||
case ElxLogVerbosity::VeryVerbose: return ELogVerbosity::VeryVerbose;
|
||||
case ElxLogVerbosity::Fatal: return ELogVerbosity::Fatal;
|
||||
}
|
||||
}
|
||||
|
||||
void UlxBlueprintErrorLibrary::FormatErrorInternal(UObject *Context, ElxLogVerbosity Verbosity, const FString &InPattern, TArray<FFormatArgumentData> InArgs)
|
||||
{
|
||||
// Generate the formatted string.
|
||||
//
|
||||
FText InPatternText(FText::FromString(InPattern));
|
||||
FText Message = FTextFormatter::Format(MoveTemp(InPatternText), MoveTemp(InArgs), false, false);
|
||||
FString MessageString = Message.ToString();
|
||||
|
||||
// Get the blueprint name.
|
||||
//
|
||||
// Normally, the log function expects you to pass in a filename, and a log
|
||||
// category name. We use the blueprint name for both.
|
||||
//
|
||||
// Using the blueprint name as a log category name is not technically
|
||||
// correct. However, there is no correct way to create log categories
|
||||
// from inside of blueprints. Doing it this way at least produces a reasonable
|
||||
// message inside the log. What doesn't work correctly is the log message
|
||||
// suppression system. Ie, console commands like 'log <category> verbose'
|
||||
// don't have any effect here. The design of the log message suppression
|
||||
// system is such that there just is no reasonable way to hook into it from
|
||||
// inside of blueprints.
|
||||
//
|
||||
FString BlueprintNameString = Context->GetClass()->GetName();
|
||||
auto BlueprintNameAnsi = StringCast<ANSICHAR>(*BlueprintNameString);
|
||||
FLogCategoryName BlueprintNameLogCategory(Context->GetClass()->GetFName());
|
||||
|
||||
// Output to Log
|
||||
//
|
||||
ELogVerbosity::Type VerbosityValue = ConvertElxLogVerbosity(Verbosity);
|
||||
if (VerbosityValue <= ELogVerbosity::COMPILED_IN_MINIMUM_VERBOSITY)
|
||||
{
|
||||
FMsg::Logf(BlueprintNameAnsi.Get(), 0, BlueprintNameLogCategory, VerbosityValue, TEXT("%s"), *MessageString);
|
||||
}
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBool(bool Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_BoolToText(Value);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataByte(uint8 Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Int;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueInt = Value;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataInt(int Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Int;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueInt = Value;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataInt64(int64 Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Int;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueInt = Value;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataFloat(float Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Float;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueFloat = Value;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataDouble(double Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Double;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueDouble = Value;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataText(FText Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = Value;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataString(FString Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_StringToText(Value);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataName(FName Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_NameToText(Value);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataKey(FKey Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_NameToText(Value.GetFName());
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataGender(ETextGender Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Gender;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueGender = Value;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataObject(UObject *Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_ObjectToText(Value);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataVector(const FVector &Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_VectorToText(Value);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataVector2D(const FVector2D &Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_Vector2dToText(Value);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataRotator(const FRotator &Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_RotatorToText(Value);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataTransform(const FTransform &Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_TransformToText(Value);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataLuaValues(const UlxLuaValues *Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = FText::FromString(Value->DebugString());
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataAnimationStep(const FlxAnimationStep &Value, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = FText::FromString(UlxAnimationStepLibrary::AnimationStepDebugString(Value));
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxBlueprintErrorLibrary::FormatArgumentDataBlank(const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = FText();
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxBlueprintErrorLibrary::FormatArgumentDataEnum(uint8 Value, const FString &Name, const UObject *PinSubCategoryObject)
|
||||
{
|
||||
const UEnum *Enum = Cast<const UEnum>(PinSubCategoryObject);
|
||||
FFormatArgumentData Result;
|
||||
if (Enum == nullptr)
|
||||
{
|
||||
Result.ArgumentValueType = EFormatArgumentType::Int;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueInt = Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = FText::Format(INVTEXT("<{0}>"), Enum->GetDisplayNameTextByValue(Value));
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
FlxDebugBlueprintErrorsOutputDevice::FlxDebugBlueprintErrorsOutputDevice(const ElxLogVerbosity &SensitivityRef)
|
||||
: Sensitivity(SensitivityRef)
|
||||
{
|
||||
GLog->AddOutputDevice(this);
|
||||
}
|
||||
|
||||
FlxDebugBlueprintErrorsOutputDevice::~FlxDebugBlueprintErrorsOutputDevice()
|
||||
{
|
||||
GLog->RemoveOutputDevice(this);
|
||||
}
|
||||
|
||||
namespace UBreakPoint {
|
||||
static volatile int V;
|
||||
FORCENOINLINE static void OnLogFatal() {
|
||||
V = 0;
|
||||
}
|
||||
FORCENOINLINE static void OnLogError() {
|
||||
V = 1;
|
||||
OnLogFatal();
|
||||
}
|
||||
FORCENOINLINE static void OnLogWarning() {
|
||||
V = 2;
|
||||
OnLogError();
|
||||
}
|
||||
}
|
||||
|
||||
static const FName LogBlueprintDebugName(TEXT("LogBlueprintDebug"));
|
||||
|
||||
|
||||
void FlxDebugBlueprintErrorsOutputDevice::Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& Category)
|
||||
{
|
||||
// If the error isn't serious enough, do nothing.
|
||||
//
|
||||
if (Verbosity > UlxBlueprintErrorLibrary::ConvertElxLogVerbosity(Sensitivity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If the Category is LogBlueprintDebug, then we're inside the debugger already.
|
||||
//
|
||||
if (Category == LogBlueprintDebugName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Find out if we're running in a blueprint thread. If not, return.
|
||||
//
|
||||
FFrame* Frame = FFrame::GetThreadLocalTopStackFrame();
|
||||
if (Frame == nullptr) return;
|
||||
UObject *TopObject = Frame->Object;
|
||||
if (TopObject == nullptr) return;
|
||||
|
||||
// Notify the debugger that there's been an exception.
|
||||
//
|
||||
FBlueprintExceptionInfo ExceptionInfo(EBlueprintExceptionType::Breakpoint, FText::FromStringView(FStringView(V)));
|
||||
FBlueprintCoreDelegates::ThrowScriptException(TopObject, *Frame, ExceptionInfo);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
//
|
||||
// BlueprintErrors: Better error handling for blueprint errors.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Containers/Array.h"
|
||||
#include "CoreMinimal.h"
|
||||
#include "InputCoreTypes.h"
|
||||
#include "HAL/Platform.h"
|
||||
#include "Misc/OutputDeviceError.h"
|
||||
#include "UObject/NameTypes.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "UObject/UObjectGlobals.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
|
||||
#include "BlueprintErrors.generated.h"
|
||||
|
||||
class UlxLuaValues;
|
||||
struct FlxAnimationStep;
|
||||
|
||||
/*
|
||||
* enum class ElxLogVerbosity, below, contains all the same error severity levels
|
||||
* as ELogVerbosity, but in a form that the blueprint editor can manipulate.
|
||||
*
|
||||
* We deliberately moved 'Fatal' to the end of the list, and made 'Error' option 0.
|
||||
* We did that because we want the editor to default to 'Error' in most cases.
|
||||
* Unfortunately, that means the numeric values of the two enums don't match up,
|
||||
* so we will need a conversion function.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/** Log Verbosity: The importance of an error message, which affects which logs the error
|
||||
* message gets written to, and how that message gets filtered.
|
||||
*
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class ElxLogVerbosity : uint8 {
|
||||
|
||||
/* Prints an error to the console and log file. The editor collects and reports errors. */
|
||||
Error,
|
||||
|
||||
/* Prints a warning to the console and log file. The editor collects and report warnings. */
|
||||
Warning,
|
||||
|
||||
/* Prints a message to the console and log file. */
|
||||
Display,
|
||||
|
||||
/* Prints a message to the log file, however, it does not print to the console. */
|
||||
Log,
|
||||
|
||||
/* Prints a message to a log file only if Verbose logging is enabled for the given category. This is usually used for detailed logging. */
|
||||
Verbose,
|
||||
|
||||
/* Prints a message to a log file. If VeryVerbose logging is enabled, then this is used for detailed logging that would otherwise spam output. */
|
||||
VeryVerbose,
|
||||
|
||||
/* Danger! Prints a fatal error to the console and log file, then crashes (this crashes the editor too). */
|
||||
Fatal,
|
||||
};
|
||||
|
||||
/* A library containing assorted useful functions for blueprint error handling.
|
||||
*
|
||||
*/
|
||||
UCLASS(MinimalAPI)
|
||||
class UlxBlueprintErrorLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// The Format Error Message blueprint node macroexpands, the following
|
||||
// function is the core of the expansion. The actual K2Node itself is in
|
||||
// its own source file.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, meta=(WorldContext = "Context", BlueprintInternalUseOnly = "true"))
|
||||
static void FormatErrorInternal(UObject *Context, ElxLogVerbosity Verbosity, const FString &InPattern, TArray<FFormatArgumentData> InArgs);
|
||||
|
||||
// A formatting routine for pins that were never connected.
|
||||
//
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataBlank(const FString &Name);
|
||||
|
||||
// A specialized formatting routine for pins of enum types.
|
||||
//
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataEnum(uint8 Value, const FString &Name, const UObject *PinSubCategoryObject);
|
||||
|
||||
// Convert an ElxLogVerbosity to an ELogVerbosity::Type
|
||||
//
|
||||
static ELogVerbosity::Type ConvertElxLogVerbosity(ElxLogVerbosity Verbosity);
|
||||
};
|
||||
|
||||
/*
|
||||
* A library that contains functions that convert data into FFormatArgumentData
|
||||
* structs, so that the data can be passed to FText::Format.
|
||||
*/
|
||||
UCLASS(MinimalAPI)
|
||||
class UlxFormatDataLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataBool(bool Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataByte(uint8 Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataInt(int Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataInt64(int64 Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataFloat(float Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataDouble(double Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataText(FText Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataString(FString Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataName(FName Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataKey(FKey Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataGender(ETextGender Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataObject(UObject *Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataVector(const FVector &Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataVector2D(const FVector2D &Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataRotator(const FRotator &Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataTransform(const FTransform &Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataLuaValues(const UlxLuaValues *Value, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataAnimationStep(const FlxAnimationStep &Value, const FString &Name);
|
||||
};
|
||||
|
||||
/* Debug Blueprint Errors output device.
|
||||
*
|
||||
* When an error message gets written to the log, using "Format Error Message,"
|
||||
* or any other means that writes an error message to the log,
|
||||
* we can optionally notify the blueprint debugger to pause execution.
|
||||
* This only affects errors that are generated during blueprint execution.
|
||||
* Errors in other threads do not pause the blueprint.
|
||||
*
|
||||
*/
|
||||
struct FlxDebugBlueprintErrorsOutputDevice : public FOutputDevice
|
||||
{
|
||||
public:
|
||||
// The constructor and destructor automatically register this output device with GLog.
|
||||
//
|
||||
// This struct doesn't store the sensitivity threshold. It relies on some blueprint
|
||||
// class to do that, so that the threshold can be easily edited with the blueprint
|
||||
// editor. This struct must be initialized with a reference to the threshold variable.
|
||||
//
|
||||
FlxDebugBlueprintErrorsOutputDevice(const ElxLogVerbosity &SensitivityRef);
|
||||
~FlxDebugBlueprintErrorsOutputDevice();
|
||||
|
||||
// Inspect a log message.
|
||||
//
|
||||
INTEGRATION_API virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& Category) override;
|
||||
|
||||
// If the device is marked 'CanBeUsedOnMultipleThreads,' then UE_LOG will
|
||||
// call Serialize from the current thread, otherwise, it will call Serialize from
|
||||
// the logging thread. Using the logging thread would defeat the purpose of this
|
||||
// device, so it's imperative that we set this flag.
|
||||
//
|
||||
INTEGRATION_API virtual bool CanBeUsedOnMultipleThreads() const override { return true; }
|
||||
|
||||
private:
|
||||
const ElxLogVerbosity &Sensitivity;
|
||||
};
|
||||
397
Source/Integration/BlueprintExporter.cpp
Normal file
397
Source/Integration/BlueprintExporter.cpp
Normal file
@@ -0,0 +1,397 @@
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "BlueprintExporter.h"
|
||||
#include "Common.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "EdGraphSchema_K2.h"
|
||||
#include "K2Node_Knot.h"
|
||||
#include "EdGraphNode_Comment.h"
|
||||
#include "K2Node_VariableGet.h"
|
||||
#include "K2Node_CallFunction.h"
|
||||
#include "K2Node_FunctionEntry.h"
|
||||
|
||||
FlxBlueprintExporter::FlxBlueprintExporter(UEdGraph* InGraph)
|
||||
: Graph(InGraph)
|
||||
{
|
||||
SortNodes();
|
||||
AssignNodeNames();
|
||||
EmitLocalVariables();
|
||||
EmitNodeList();
|
||||
EmitGraph();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// General utilities for manipulating UEdGraph nodes.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
FString FlxBlueprintExporter::SanitizeName(const FString& Title)
|
||||
{
|
||||
FString Result = Title.TrimStartAndEnd().Replace(TEXT(" "), TEXT("_"));
|
||||
return Result.IsEmpty() ? TEXT("_") : Result;
|
||||
}
|
||||
|
||||
FString FlxBlueprintExporter::FormatPinType(const FEdGraphPinType& PinType)
|
||||
{
|
||||
if (UObject* SubObj = PinType.PinSubCategoryObject.Get())
|
||||
{
|
||||
return SubObj->GetName();
|
||||
}
|
||||
FString Type = PinType.PinCategory.ToString();
|
||||
Type[0] = FChar::ToUpper(Type[0]);
|
||||
return Type;
|
||||
}
|
||||
|
||||
FString FlxBlueprintExporter::FormatPinType(UEdGraphPin* Pin)
|
||||
{
|
||||
return FormatPinType(Pin->PinType);
|
||||
}
|
||||
|
||||
FString FlxBlueprintExporter::FormatNodeBaseName(UEdGraphNode* Node)
|
||||
{
|
||||
FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
|
||||
int32 NewlineIdx;
|
||||
if (Title.FindChar(TEXT('\n'), NewlineIdx))
|
||||
Title.LeftInline(NewlineIdx);
|
||||
return SanitizeName(Title);
|
||||
}
|
||||
|
||||
UEdGraphPin* FlxBlueprintExporter::GetLinkedTo(UEdGraphPin* Pin)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (Pin == nullptr) return nullptr;
|
||||
if (Pin->LinkedTo.IsEmpty()) return nullptr;
|
||||
UEdGraphPin *LinkedTo = Pin->LinkedTo[0];
|
||||
if (!LinkedTo->GetOwningNode()->IsA<UK2Node_Knot>()) return LinkedTo;
|
||||
Pin = FindFirstPin(LinkedTo->GetOwningNode(), Pin->Direction);
|
||||
}
|
||||
}
|
||||
|
||||
bool FlxBlueprintExporter::IsDefaultToSelf(UEdGraphPin* Pin)
|
||||
{
|
||||
// Only valid call-function nodes can have default-to-self.
|
||||
UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(Pin->GetOwningNode());
|
||||
if (!CallNode) return false;
|
||||
UFunction* Function = CallNode->GetTargetFunction();
|
||||
if (!Function) return false;
|
||||
|
||||
// In a call function node, the pin name 'self' is reserved
|
||||
// for the C++ 'this' pointer. This always has 'DefaultToSelf'
|
||||
// behavior. Note that 'self' in other nodes is not special.
|
||||
if (Pin->PinName == UEdGraphSchema_K2::PN_Self) return true;
|
||||
|
||||
// For any other pin name, we have to check for
|
||||
// the presence of meta = (DefaultToSelf = "Pin")
|
||||
const FString& DefaultToSelfPinName = Function->GetMetaData(FBlueprintMetadata::MD_DefaultToSelf);
|
||||
return Pin->PinName.ToString() == DefaultToSelfPinName;
|
||||
}
|
||||
|
||||
TArray<UEdGraphPin*> FlxBlueprintExporter::FilterPins(UEdGraphNode* Node, EEdGraphPinDirection Direction, FName Category)
|
||||
{
|
||||
TArray<UEdGraphPin*> Result;
|
||||
for (UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (Direction != EGPD_MAX && Pin->Direction != Direction) continue;
|
||||
if (!Category.IsNone() && Pin->PinType.PinCategory != Category) continue;
|
||||
Result.Add(Pin);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool FlxBlueprintExporter::HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
||||
{
|
||||
return FilterPins(Node, Direction, UEdGraphSchema_K2::PC_Exec).Num() > 0;
|
||||
}
|
||||
|
||||
UEdGraphPin* FlxBlueprintExporter::FindFirstPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
||||
{
|
||||
for (UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (Pin->Direction == Direction) return Pin;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UEdGraphPin* FlxBlueprintExporter::BestMatchPin(UEdGraphNode* Node, EEdGraphPinDirection Direction, bool Exec, const FString& Name)
|
||||
{
|
||||
if (Name == TEXT("_")) return nullptr;
|
||||
|
||||
UEdGraphPin* DisplayMatch = nullptr;
|
||||
int32 DisplayCount = 0;
|
||||
UEdGraphPin* RawMatch = nullptr;
|
||||
int32 RawCount = 0;
|
||||
|
||||
for (UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (Pin->Direction != Direction) continue;
|
||||
bool PinIsExec = (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec);
|
||||
if (PinIsExec != Exec) continue;
|
||||
|
||||
if (Name == SanitizeName(Node->GetPinDisplayName(Pin).ToString()))
|
||||
{
|
||||
DisplayMatch = Pin;
|
||||
DisplayCount++;
|
||||
}
|
||||
|
||||
if (Name == SanitizeName(Pin->PinName.ToString()))
|
||||
{
|
||||
RawMatch = Pin;
|
||||
RawCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (DisplayCount == 1) return DisplayMatch;
|
||||
if (RawCount == 1) return RawMatch;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FString FlxBlueprintExporter::FormatPinName(UEdGraphPin *Pin)
|
||||
{
|
||||
UEdGraphNode* Node = Pin->GetOwningNode();
|
||||
bool Exec = (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec);
|
||||
|
||||
// Try sanitized display name first.
|
||||
FString SanitizedDisplay = SanitizeName(Node->GetPinDisplayName(Pin).ToString());
|
||||
if (BestMatchPin(Node, Pin->Direction, Exec, SanitizedDisplay) == Pin)
|
||||
return SanitizedDisplay;
|
||||
|
||||
// Try sanitized raw name.
|
||||
FString SanitizedRaw = SanitizeName(Pin->PinName.ToString());
|
||||
if (BestMatchPin(Node, Pin->Direction, Exec, SanitizedRaw) == Pin)
|
||||
return SanitizedRaw;
|
||||
|
||||
// No unambiguous name found.
|
||||
UE_LOG(LogLuprexIntegration, Warning, TEXT("Blueprint export: ambiguous pin name '%s' on node '%s'"),
|
||||
*Pin->PinName.ToString(), *Node->GetNodeTitle(ENodeTitleType::ListView).ToString());
|
||||
return FString::Printf(TEXT("?%s"), *SanitizedRaw);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// Traverse and Emit the Nodes.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
FString FlxBlueprintExporter::FormatNodeName(UEdGraphNode* Node)
|
||||
{
|
||||
FString* Name = NodeNames.Find(Node);
|
||||
return Name ? *Name : FormatNodeBaseName(Node);
|
||||
}
|
||||
|
||||
FString FlxBlueprintExporter::FormatPinSource(UEdGraphPin* Pin)
|
||||
{
|
||||
// If connected, show source node.pin
|
||||
UEdGraphPin* LinkedTo = GetLinkedTo(Pin);
|
||||
if (LinkedTo != nullptr)
|
||||
{
|
||||
UEdGraphNode* LinkedToNode = LinkedTo->GetOwningNode();
|
||||
|
||||
// For variable get nodes, just show the variable name.
|
||||
if (UK2Node_VariableGet* VarGet = Cast<UK2Node_VariableGet>(LinkedToNode))
|
||||
return SanitizeName(VarGet->GetVarNameString());
|
||||
|
||||
FString PinLabel = FormatPinName(LinkedTo);
|
||||
return FString::Printf(TEXT("%s.%s"), *FormatNodeName(LinkedToNode), *PinLabel);
|
||||
}
|
||||
|
||||
// String pins: always show in quotes (even if empty).
|
||||
FName Category = Pin->PinType.PinCategory;
|
||||
if (Category == UEdGraphSchema_K2::PC_String ||
|
||||
Category == UEdGraphSchema_K2::PC_Name ||
|
||||
Category == UEdGraphSchema_K2::PC_Text)
|
||||
{
|
||||
return FString::Printf(TEXT("\"%s\""), *Pin->DefaultValue);
|
||||
}
|
||||
|
||||
// If has a non-empty default, show it.
|
||||
if (!Pin->DefaultValue.IsEmpty())
|
||||
{
|
||||
return Pin->DefaultValue;
|
||||
}
|
||||
|
||||
if (Pin->DefaultObject)
|
||||
{
|
||||
return Pin->DefaultObject->GetName();
|
||||
}
|
||||
|
||||
if (!Pin->DefaultTextValue.IsEmpty())
|
||||
{
|
||||
return FString::Printf(TEXT("\"%s\""), *Pin->DefaultTextValue.ToString());
|
||||
}
|
||||
|
||||
if (IsDefaultToSelf(Pin))
|
||||
{
|
||||
return TEXT("<self>");
|
||||
}
|
||||
else
|
||||
{
|
||||
return TEXT("<default>");
|
||||
}
|
||||
}
|
||||
|
||||
void FlxBlueprintExporter::Traverse(UEdGraphNode* Node)
|
||||
{
|
||||
if (Visited.Contains(Node)) return;
|
||||
Visited.Add(Node);
|
||||
|
||||
// First, traverse input nodes
|
||||
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input))
|
||||
for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
|
||||
Traverse(LinkedPin->GetOwningNode());
|
||||
|
||||
// Add this node to the sorted list.
|
||||
SortedNodes.Add(Node);
|
||||
|
||||
// Then, traverse exec output nodes only.
|
||||
// Data outputs are not followed — data nodes get pulled in
|
||||
// through their consumers' input traversal.
|
||||
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Output, UEdGraphSchema_K2::PC_Exec))
|
||||
for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
|
||||
Traverse(LinkedPin->GetOwningNode());
|
||||
}
|
||||
|
||||
void FlxBlueprintExporter::SortNodes()
|
||||
{
|
||||
// Find starter nodes: have exec output but no exec input.
|
||||
TArray<UEdGraphNode*> Starters;
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
{
|
||||
if (HasExecPin(Node, EGPD_Output) && !HasExecPin(Node, EGPD_Input))
|
||||
{
|
||||
Starters.Add(Node);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort starters by Y position.
|
||||
Starters.Sort([](const UEdGraphNode& A, const UEdGraphNode& B)
|
||||
{
|
||||
return A.NodePosY < B.NodePosY;
|
||||
});
|
||||
|
||||
// Traverse from each starter.
|
||||
for (UEdGraphNode* Starter : Starters)
|
||||
{
|
||||
Traverse(Starter);
|
||||
}
|
||||
|
||||
// Traverse all nodes.
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
{
|
||||
Traverse(Node);
|
||||
}
|
||||
}
|
||||
|
||||
void FlxBlueprintExporter::AssignNodeNames()
|
||||
{
|
||||
TMap<FString, int32> NextIndex;
|
||||
|
||||
for (UEdGraphNode* Node : SortedNodes)
|
||||
{
|
||||
FString Base = FormatNodeBaseName(Node);
|
||||
int32& Idx = NextIndex.FindOrAdd(Base, 0);
|
||||
FString Name = (Idx == 0) ? Base : FString::Printf(TEXT("%s_%d"), *Base, Idx + 1);
|
||||
NodeNames.Add(Node, Name);
|
||||
Idx++;
|
||||
}
|
||||
}
|
||||
|
||||
void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
|
||||
{
|
||||
if (Node->IsA<UEdGraphNode_Comment>())
|
||||
{
|
||||
Output.Appendf(TEXT("\n// %s\n"), *Node->NodeComment);
|
||||
return;
|
||||
}
|
||||
|
||||
Output.Appendf(TEXT("\nnode %s\n"), *FormatNodeName(Node));
|
||||
|
||||
// Emit input data pins.
|
||||
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input))
|
||||
{
|
||||
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) continue;
|
||||
if (Pin->bHidden) continue;
|
||||
|
||||
Output.Appendf(TEXT(" input %s %s = %s\n"),
|
||||
*FormatPinType(Pin),
|
||||
*FormatPinName(Pin),
|
||||
*FormatPinSource(Pin));
|
||||
}
|
||||
|
||||
// Emit output data pins as a return line.
|
||||
FString ReturnPins;
|
||||
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Output))
|
||||
{
|
||||
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) continue;
|
||||
if (Pin->bHidden) continue;
|
||||
if (!ReturnPins.IsEmpty()) ReturnPins += TEXT(", ");
|
||||
ReturnPins += FormatPinName(Pin);
|
||||
}
|
||||
if (!ReturnPins.IsEmpty())
|
||||
{
|
||||
Output.Appendf(TEXT(" return %s\n"), *ReturnPins);
|
||||
}
|
||||
|
||||
// Emit output exec pins as goto statements.
|
||||
TArray<UEdGraphPin*> ExecOuts = FilterPins(Node, EGPD_Output, UEdGraphSchema_K2::PC_Exec);
|
||||
for (UEdGraphPin* Pin : ExecOuts)
|
||||
{
|
||||
UEdGraphPin* LinkedTo = GetLinkedTo(Pin);
|
||||
if (!LinkedTo) continue;
|
||||
|
||||
FString Target = FormatNodeName(LinkedTo->GetOwningNode());
|
||||
|
||||
if (ExecOuts.Num() == 1)
|
||||
Output.Appendf(TEXT(" goto %s\n"), *Target);
|
||||
else
|
||||
Output.Appendf(TEXT(" goto-if %s %s\n"), *FormatPinName(Pin), *Target);
|
||||
}
|
||||
}
|
||||
|
||||
void FlxBlueprintExporter::EmitLocalVariables()
|
||||
{
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
{
|
||||
UK2Node_FunctionEntry* EntryNode = Cast<UK2Node_FunctionEntry>(Node);
|
||||
if (!EntryNode) continue;
|
||||
|
||||
for (const FBPVariableDescription& Var : EntryNode->LocalVariables)
|
||||
{
|
||||
FString Default = Var.DefaultValue.IsEmpty() ? TEXT("<default>") : Var.DefaultValue;
|
||||
Output.Appendf(TEXT("local %s %s = %s\n"),
|
||||
*FormatPinType(Var.VarType),
|
||||
*SanitizeName(Var.VarName.ToString()),
|
||||
*Default);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FlxBlueprintExporter::EmitGraph()
|
||||
{
|
||||
for (UEdGraphNode* Node : SortedNodes)
|
||||
{
|
||||
if (Node->IsA<UK2Node_Knot>()) continue;
|
||||
if (Node->IsA<UK2Node_VariableGet>()) continue;
|
||||
EmitNode(Node);
|
||||
}
|
||||
}
|
||||
|
||||
void FlxBlueprintExporter::EmitNodeList()
|
||||
{
|
||||
for (UEdGraphNode* Node : SortedNodes)
|
||||
{
|
||||
if (Node->IsA<UK2Node_Knot>()) continue;
|
||||
if (Node->IsA<UEdGraphNode_Comment>()) continue;
|
||||
if (Node->IsA<UK2Node_VariableGet>()) continue;
|
||||
Details.Appendf(TEXT("%s = %s\n"),
|
||||
*FormatNodeName(Node), *Node->NodeGuid.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
128
Source/Integration/BlueprintExporter.h
Normal file
128
Source/Integration/BlueprintExporter.h
Normal file
@@ -0,0 +1,128 @@
|
||||
#pragma once
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
|
||||
class FlxBlueprintExporter
|
||||
{
|
||||
public:
|
||||
FlxBlueprintExporter(UEdGraph* InGraph);
|
||||
|
||||
const FString GetOutput() { return Output.ToString(); }
|
||||
const FString GetDetails() { return Details.ToString(); }
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// General utilities for manipulating UEdGraph nodes.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
// Sanitize a name: trim whitespace, and replace space
|
||||
// with underscores.
|
||||
//
|
||||
static FString SanitizeName(const FString& Title);
|
||||
|
||||
// Get the pin type as a string. This is lossy,
|
||||
// we don't differentiate between object reference
|
||||
// or struct, for example. But that's OK, we don't
|
||||
// need precise type info, we just need readability.
|
||||
//
|
||||
static FString FormatPinType(const FEdGraphPinType& PinType);
|
||||
static FString FormatPinType(UEdGraphPin* Pin);
|
||||
|
||||
// Get the node base name as a sanitized string.
|
||||
// Later, we may append a number to this base name
|
||||
// in order to turn it into a unique string.
|
||||
//
|
||||
static FString FormatNodeBaseName(UEdGraphNode* Node);
|
||||
|
||||
// Get the pin that this pin is linked to. If the
|
||||
// pin is linked to multiple pins, returns the first.
|
||||
// If the pin is linked to a knot node, follow the
|
||||
// chain of knot nodes and find the pin at the other
|
||||
// end of the chain. Returns nullptr if this pin
|
||||
// is not linked to anything.
|
||||
//
|
||||
static UEdGraphPin* GetLinkedTo(UEdGraphPin *Pin);
|
||||
|
||||
// Return true if the pin in question defaults
|
||||
// to self.
|
||||
//
|
||||
static bool IsDefaultToSelf(UEdGraphPin* Pin);
|
||||
|
||||
// Get a subset of the pins in the node, filtered
|
||||
// by direction, category, or both.
|
||||
//
|
||||
static TArray<UEdGraphPin*> FilterPins(UEdGraphNode* Node,
|
||||
EEdGraphPinDirection Direction = EGPD_MAX, FName Category = FName());
|
||||
|
||||
// Return true if the node has an exec pin that points
|
||||
// in the specified direction.
|
||||
//
|
||||
static bool HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction);
|
||||
|
||||
// Find the first pin that points in the specified direction.
|
||||
//
|
||||
static UEdGraphPin* FindFirstPin(UEdGraphNode* Node, EEdGraphPinDirection Direction);
|
||||
|
||||
// Given a sanitized pin display name or a sanitized pin
|
||||
// name, find the one pin that matches. If the string
|
||||
// provided doesn't match any pin, or if it matches
|
||||
// multiple pins ambiguously, returns nullptr. If the
|
||||
// string is the sanitized empty string, returns nullptr
|
||||
// even if that matches a pin.
|
||||
//
|
||||
static UEdGraphPin* BestMatchPin(UEdGraphNode* Node, EEdGraphPinDirection Direction, bool Exec, const FString& Name);
|
||||
|
||||
// Returns either the sanitized display name or
|
||||
// sanitized pin name. Chooses the one that
|
||||
// unambiguously identifies the pin. If neither is
|
||||
// ambiguous, prefers the display name. If both are
|
||||
// ambiguous, returns the display name with a question
|
||||
// mark prepended to indicate that it doesn't uniquely
|
||||
// identify the pin.
|
||||
//
|
||||
static FString FormatPinName(UEdGraphPin *Pin);
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// Traverse and Emit the Nodes.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
FString FormatNodeName(UEdGraphNode* Node);
|
||||
FString FormatPinSource(UEdGraphPin* Pin);
|
||||
void Traverse(UEdGraphNode* Node);
|
||||
void SortNodes();
|
||||
void AssignNodeNames();
|
||||
void EmitNode(UEdGraphNode* Node);
|
||||
void EmitLocalVariables();
|
||||
void EmitGraph();
|
||||
void EmitNodeList();
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// Values recorded during traversal.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
UEdGraph* Graph;
|
||||
|
||||
// Data populated by passes.
|
||||
TMap<UEdGraphNode*, FString> NodeNames;
|
||||
TArray<UEdGraphNode*> SortedNodes;
|
||||
TSet<UEdGraphNode*> Visited;
|
||||
|
||||
// Output buffers.
|
||||
TStringBuilder<4096> Output;
|
||||
TStringBuilder<4096> Details;
|
||||
};
|
||||
|
||||
#endif
|
||||
76
Source/Integration/BreakToDebugger.cpp
Normal file
76
Source/Integration/BreakToDebugger.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
#include "BreakToDebugger.h"
|
||||
#include "Blueprint/BlueprintExceptionInfo.h"
|
||||
#include "Kismet2/KismetDebugUtilities.h"
|
||||
|
||||
ELogVerbosity::Type FlxBreakToDebuggerOutputDevice::ConvertThreshold(ElxBreakToDebuggerThreshold Verbosity) {
|
||||
switch (Verbosity) {
|
||||
case ElxBreakToDebuggerThreshold::Error: return ELogVerbosity::Error;
|
||||
case ElxBreakToDebuggerThreshold::Warning: return ELogVerbosity::Warning;
|
||||
case ElxBreakToDebuggerThreshold::Display: return ELogVerbosity::Display;
|
||||
case ElxBreakToDebuggerThreshold::Log: return ELogVerbosity::Log;
|
||||
case ElxBreakToDebuggerThreshold::Verbose: return ELogVerbosity::Verbose;
|
||||
case ElxBreakToDebuggerThreshold::VeryVerbose: return ELogVerbosity::VeryVerbose;
|
||||
case ElxBreakToDebuggerThreshold::Fatal: return ELogVerbosity::Fatal;
|
||||
}
|
||||
}
|
||||
|
||||
FlxBreakToDebuggerOutputDevice::FlxBreakToDebuggerOutputDevice(const ElxBreakToDebuggerThreshold &SensitivityRef)
|
||||
: Sensitivity(SensitivityRef)
|
||||
{
|
||||
GLog->AddOutputDevice(this);
|
||||
}
|
||||
|
||||
FlxBreakToDebuggerOutputDevice::~FlxBreakToDebuggerOutputDevice()
|
||||
{
|
||||
GLog->RemoveOutputDevice(this);
|
||||
}
|
||||
|
||||
namespace UBreakPoint {
|
||||
static volatile int V;
|
||||
FORCENOINLINE static void OnLogFatal() {
|
||||
V = 0;
|
||||
}
|
||||
FORCENOINLINE static void OnLogError() {
|
||||
V = 1;
|
||||
OnLogFatal();
|
||||
}
|
||||
FORCENOINLINE static void OnLogWarning() {
|
||||
V = 2;
|
||||
OnLogError();
|
||||
}
|
||||
}
|
||||
|
||||
static const FName LogBlueprintDebugName(TEXT("LogBlueprintDebug"));
|
||||
|
||||
|
||||
void FlxBreakToDebuggerOutputDevice::Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& Category)
|
||||
{
|
||||
// If the error isn't serious enough, do nothing.
|
||||
//
|
||||
if (Verbosity > ConvertThreshold(Sensitivity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If the Category is LogBlueprintDebug, then we're inside the debugger already.
|
||||
//
|
||||
if (Category == LogBlueprintDebugName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Find out if we're running in a blueprint thread. If not, return.
|
||||
//
|
||||
FFrame* Frame = FFrame::GetThreadLocalTopStackFrame();
|
||||
if (Frame == nullptr) return;
|
||||
UObject *TopObject = Frame->Object;
|
||||
if (TopObject == nullptr) return;
|
||||
|
||||
// Notify the debugger that there's been an exception.
|
||||
//
|
||||
FBlueprintExceptionInfo ExceptionInfo(EBlueprintExceptionType::Breakpoint, FText::FromStringView(FStringView(V)));
|
||||
FBlueprintCoreDelegates::ThrowScriptException(TopObject, *Frame, ExceptionInfo);
|
||||
}
|
||||
|
||||
|
||||
113
Source/Integration/BreakToDebugger.h
Normal file
113
Source/Integration/BreakToDebugger.h
Normal file
@@ -0,0 +1,113 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// BreakToDebugger
|
||||
//
|
||||
// When an error message gets written to UE_LOG, we can
|
||||
// optionally trigger the blueprint debugger.
|
||||
//
|
||||
// This only affects UE_LOG messages that are generated
|
||||
// during blueprint execution. Log messages from other
|
||||
// threads do not trigger the debugger.
|
||||
//
|
||||
// The following explains how we trigger the blueprint
|
||||
// debugger on UE_LOG messages. Log messages are sent to a
|
||||
// long list of output devices, including: the visual studio
|
||||
// output window, the unreal editor output window, the log
|
||||
// file, and so forth. We add another pseudo output device.
|
||||
// This output device doesn't actually send the log message
|
||||
// anywhere, instead, it just activates the blueprint
|
||||
// debugger.
|
||||
//
|
||||
// UE_LOG messages can be generated from any thread. The
|
||||
// pseudo output device checks what thread it is running in,
|
||||
// and if it's not the blueprint thread, it does nothing at
|
||||
// all. If it is the blueprint thread, it's safe to trigger
|
||||
// the blueprint debugger.
|
||||
//
|
||||
// One annoying limitation of this design is that our output
|
||||
// device ends up early in the list, so the debugger runs
|
||||
// *before* the message shows up in visual studio or the
|
||||
// unreal editor. As a result, when you are in the
|
||||
// debugger, the message won't be in your output window.
|
||||
// Pressing 'single step' always reveals the message.
|
||||
//
|
||||
// The blueprint node "Format Log Message" uses UE_LOG
|
||||
// internally, so therefore, that too can trigger the
|
||||
// blueprint debugger.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Containers/Array.h"
|
||||
#include "CoreMinimal.h"
|
||||
#include "HAL/Platform.h"
|
||||
#include "Misc/OutputDeviceError.h"
|
||||
#include "UObject/NameTypes.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "UObject/UObjectGlobals.h"
|
||||
#include "BreakToDebugger.generated.h"
|
||||
|
||||
// ElxBreakToDebuggerThreshold:
|
||||
//
|
||||
// Controls the sensitivity level at which UE_LOG messages
|
||||
// trigger the blueprint debugger.
|
||||
//
|
||||
UENUM(BlueprintType)
|
||||
enum class ElxBreakToDebuggerThreshold : uint8 {
|
||||
|
||||
/** Break on errors. */
|
||||
Error,
|
||||
|
||||
/** Break on warnings and above. */
|
||||
Warning,
|
||||
|
||||
/** Break on display messages and above. */
|
||||
Display,
|
||||
|
||||
/** Break on log messages and above. */
|
||||
Log,
|
||||
|
||||
/** Break on verbose messages and above. */
|
||||
Verbose,
|
||||
|
||||
/** Break on all messages. */
|
||||
VeryVerbose,
|
||||
|
||||
/** Break on fatal errors only (ie, never break -- the process crashes instead). */
|
||||
Fatal,
|
||||
};
|
||||
|
||||
|
||||
struct FlxBreakToDebuggerOutputDevice : public FOutputDevice
|
||||
{
|
||||
public:
|
||||
// The constructor and destructor automatically register
|
||||
// this output device with GLog.
|
||||
//
|
||||
// This struct doesn't store the sensitivity threshold.
|
||||
// It relies on the LuprexGameMode class to do that, so
|
||||
// that the threshold can be easily edited with the
|
||||
// blueprint editor. This struct must be initialized
|
||||
// with a reference to the threshold variable.
|
||||
//
|
||||
FlxBreakToDebuggerOutputDevice(const ElxBreakToDebuggerThreshold &SensitivityRef);
|
||||
~FlxBreakToDebuggerOutputDevice();
|
||||
|
||||
// Inspect a log message.
|
||||
//
|
||||
INTEGRATION_API virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& Category) override;
|
||||
|
||||
// If the device is marked 'CanBeUsedOnMultipleThreads,'
|
||||
// then UE_LOG will call Serialize from the current
|
||||
// thread, otherwise, it will call Serialize from the
|
||||
// logging thread. Using the logging thread would
|
||||
// defeat the purpose of this device, so it's imperative
|
||||
// that we set this flag.
|
||||
//
|
||||
INTEGRATION_API virtual bool CanBeUsedOnMultipleThreads() const override { return true; }
|
||||
|
||||
private:
|
||||
static ELogVerbosity::Type ConvertThreshold(ElxBreakToDebuggerThreshold Verbosity);
|
||||
const ElxBreakToDebuggerThreshold &Sensitivity;
|
||||
};
|
||||
@@ -1,37 +1,50 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Common.h
|
||||
//
|
||||
// Simple data types used throughout the Unreal
|
||||
// interface to Luprex: type aliases, blueprint
|
||||
// enums for branching, and log categories.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "Common.generated.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// A bunch of simple data types that are used throughout the
|
||||
// unreal interface to Luprex.
|
||||
// LpxCommonTypes
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Type aliases used throughout the integration.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
namespace LpxCommonTypes {
|
||||
// Array of tangible IDs.
|
||||
//
|
||||
using IdArray = TArray<int64>;
|
||||
|
||||
// View of Array of tangible IDs.
|
||||
//
|
||||
using IdView = TArrayView<const int64>;
|
||||
|
||||
// Array of std::string_view
|
||||
// Array of std::string_view.
|
||||
//
|
||||
using StringViewVec = TArray<std::string_view>;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Luprex exports a header-only library called "base-buffer.hpp",
|
||||
// which includes a "struct LuaValueHolder" which contains a tag
|
||||
// field of type "enum LuaValueType." The following enum is a
|
||||
// direct copy of that enum, and the values must match one-for-one.
|
||||
// Note that "token" has been renamed to "name", those are
|
||||
// synonymous.
|
||||
// ElxLuaValueType
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Mirror of LuaValueType from base-buffer.hpp.
|
||||
// Values must match one-for-one. Note that "token"
|
||||
// has been renamed to "name" (synonymous).
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class ElxLuaValueType : uint8 {
|
||||
@@ -43,13 +56,14 @@ enum class ElxLuaValueType : uint8 {
|
||||
Vector
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// A variety of boolean-like results that can be conveniently
|
||||
// be used in conjunction with 'ExpandEnumAsExecs' to create
|
||||
// branching functions.
|
||||
// Branching Enums
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Boolean-like results used with ExpandEnumAsExecs
|
||||
// to create branching blueprint functions.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class ElxSuccessOrError : uint8 {
|
||||
@@ -81,16 +95,39 @@ enum class ElxSuccessOrWrongType : uint8 {
|
||||
WrongType,
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Two log categories that are used throughout the Unreal
|
||||
// Luprex integration.
|
||||
// ElxLuaSyntaxCheck
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Classifies console commands syntactically:
|
||||
//
|
||||
// SlashCommand: starts with a slash, not lua
|
||||
// Whitespace: the input only contains whitespace
|
||||
// ValidLua: the input is valid lua code
|
||||
// TruncatedLua: the input is truncated
|
||||
// InvalidLua: invalid lua
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class ElxLuaSyntaxCheck : uint8 {
|
||||
SlashCommand,
|
||||
Whitespace,
|
||||
ValidLua,
|
||||
TruncatedLua,
|
||||
InvalidLua,
|
||||
};
|
||||
|
||||
// Messages that come from inside the Luprex Core.
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Log Categories
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
// Messages from inside the Luprex Core.
|
||||
//
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogLuprex, Display, All);
|
||||
|
||||
// Messages that pertain to our Luprex integration with Unreal.
|
||||
// Messages about the Luprex integration with Unreal.
|
||||
//
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogLuprexIntegration, Display, All);
|
||||
|
||||
@@ -1,13 +1,32 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// ConsoleOutput.h
|
||||
//
|
||||
// Optional helper for the console window. The
|
||||
// GameMode blueprint can use this class to store
|
||||
// console text as one big string with newlines.
|
||||
//
|
||||
// A dirty bit is set whenever text is appended,
|
||||
// so the blueprint only needs to update the widget
|
||||
// when the text has actually changed.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Containers/UnrealString.h"
|
||||
|
||||
#include "ConsoleOutput.generated.h"
|
||||
|
||||
<<<<<<< HEAD
|
||||
//////////////////////////////////////////////////////////////
|
||||
=======
|
||||
////////////////////////////////////////////////////////////
|
||||
>>>>>>> 9b1dd00a45a7b17c3546f8574d00e5ec78f17c75
|
||||
//
|
||||
// ConsoleOutput
|
||||
// UlxConsoleOutput
|
||||
//
|
||||
<<<<<<< HEAD
|
||||
// When the lua code executes a print statement, the text
|
||||
// eventually gets passed to the GameMode blueprint: see
|
||||
// Docs/Print-Statement-Handling.md for more information.
|
||||
@@ -34,6 +53,9 @@
|
||||
// implement the virtual console, that's perfectly fine.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
=======
|
||||
////////////////////////////////////////////////////////////
|
||||
>>>>>>> 9b1dd00a45a7b17c3546f8574d00e5ec78f17c75
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class UlxConsoleOutput : public UObject
|
||||
@@ -45,36 +67,46 @@ private:
|
||||
bool Dirty;
|
||||
|
||||
private:
|
||||
// Truncate the console to a reasonable number of
|
||||
// lines. The length is hardwired.
|
||||
// Truncate the console to a reasonable number of lines.
|
||||
// The length is hardwired.
|
||||
//
|
||||
void Truncate();
|
||||
|
||||
// Add a newline if there isn't one. Returns true if it changed anything.
|
||||
// Add a newline if there isn't one.
|
||||
//
|
||||
// Returns true if it changed anything.
|
||||
//
|
||||
bool MaybeAppendNewline();
|
||||
|
||||
// Append text. Returns true if it changed anything.
|
||||
// Append text.
|
||||
//
|
||||
// Returns true if it changed anything.
|
||||
//
|
||||
bool MaybeAppendText(const FString& text);
|
||||
|
||||
public:
|
||||
// Append a line of text to the console.
|
||||
// Append text to the console.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void Append(const FString& text);
|
||||
|
||||
// Append a line of text to the console on a line by itself.
|
||||
// Append text on a line by itself.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void AppendLine(const FString& text);
|
||||
|
||||
// Get the console text as a string.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable)
|
||||
const FString& Get() const { return Content; }
|
||||
|
||||
// Return if the dirty flag is set.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable)
|
||||
bool IsDirty() const { return Dirty; }
|
||||
|
||||
// Clear the dirty flag.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void ClearDirty() { Dirty = false; }
|
||||
};
|
||||
|
||||
|
||||
|
||||
205
Source/Integration/FormatDataLibrary.cpp
Normal file
205
Source/Integration/FormatDataLibrary.cpp
Normal file
@@ -0,0 +1,205 @@
|
||||
|
||||
#include "FormatDataLibrary.h"
|
||||
#include "LuaCall.h"
|
||||
#include "AnimQueue.h"
|
||||
#include "MovementComponentState.h"
|
||||
#include "Kismet/KismetTextLibrary.h"
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBool(bool AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_BoolToText(AutoConvertedValue);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataByte(uint8 AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Int;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueInt = AutoConvertedValue;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataInt(int AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Int;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueInt = AutoConvertedValue;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataInt64(int64 AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Int;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueInt = AutoConvertedValue;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataFloat(float AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Float;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueFloat = AutoConvertedValue;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataDouble(double AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Double;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueDouble = AutoConvertedValue;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataText(FText AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = AutoConvertedValue;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataString(FString AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_StringToText(AutoConvertedValue);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataName(FName AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_NameToText(AutoConvertedValue);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataKey(FKey AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_NameToText(AutoConvertedValue.GetFName());
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataGender(ETextGender AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Gender;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueGender = AutoConvertedValue;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataObject(UObject *AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_ObjectToText(AutoConvertedValue);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataVector(const FVector &AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_VectorToText(AutoConvertedValue);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataVector2D(const FVector2D &AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_Vector2dToText(AutoConvertedValue);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataRotator(const FRotator &AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_RotatorToText(AutoConvertedValue);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataTransform(const FTransform &AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = UKismetTextLibrary::Conv_TransformToText(AutoConvertedValue);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataLuaValues(const UlxLuaValues *AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = FText::FromString(AutoConvertedValue->DebugString());
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataAnimationStep(const FlxAnimationStep &AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = FText::FromString(UlxAnimationStepLibrary::AnimationStepDebugString(AutoConvertedValue));
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataMovementComponentState(const FlxMovementComponentState &AutoConvertedValue, const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = FText::FromString(UlxMovementComponentStateLibrary::DebugString(AutoConvertedValue));
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBlank(const FString &Name)
|
||||
{
|
||||
FFormatArgumentData Result;
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = FText();
|
||||
return Result;
|
||||
}
|
||||
|
||||
FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataEnum(uint8 Value, const FString &Name, const UObject *PinSubCategoryObject)
|
||||
{
|
||||
const UEnum *Enum = Cast<const UEnum>(PinSubCategoryObject);
|
||||
FFormatArgumentData Result;
|
||||
if (Enum == nullptr)
|
||||
{
|
||||
Result.ArgumentValueType = EFormatArgumentType::Int;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValueInt = Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Result.ArgumentValueType = EFormatArgumentType::Text;
|
||||
Result.ArgumentName = Name;
|
||||
Result.ArgumentValue = FText::Format(INVTEXT("<{0}>"), Enum->GetDisplayNameTextByValue(Value));
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
110
Source/Integration/FormatDataLibrary.h
Normal file
110
Source/Integration/FormatDataLibrary.h
Normal file
@@ -0,0 +1,110 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FormatDataLibrary.h
|
||||
//
|
||||
// Functions that convert data into
|
||||
// FFormatArgumentData structs, so that the data
|
||||
// can be passed to FText::Format.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "InputCoreTypes.h"
|
||||
#include "HAL/Platform.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "UObject/UObjectGlobals.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
|
||||
#include "FormatDataLibrary.generated.h"
|
||||
|
||||
class UlxLuaValues;
|
||||
struct FlxAnimationStep;
|
||||
struct FlxMovementComponentState;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// UlxFormatDataLibrary
|
||||
//
|
||||
// The FormatLogMessage K2Node scans this library using
|
||||
// reflection, looking for functions that have a parameter
|
||||
// named "AutoConvertedValue". It uses the type of that
|
||||
// parameter to determine which function to call for a given
|
||||
// pin type.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
UCLASS(MinimalAPI)
|
||||
class UlxFormatDataLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataBool(bool AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataByte(uint8 AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataInt(int AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataInt64(int64 AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataFloat(float AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataDouble(double AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataText(FText AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataString(FString AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataName(FName AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataKey(FKey AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataGender(ETextGender AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataObject(UObject *AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataVector(const FVector &AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataVector2D(const FVector2D &AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataRotator(const FRotator &AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataTransform(const FTransform &AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataLuaValues(const UlxLuaValues *AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataAnimationStep(const FlxAnimationStep &AutoConvertedValue, const FString &Name);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataMovementComponentState(const FlxMovementComponentState &AutoConvertedValue, const FString &Name);
|
||||
|
||||
// For pins that were never connected.
|
||||
//
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataBlank(const FString &Name);
|
||||
|
||||
// For pins of enum types.
|
||||
//
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility")
|
||||
static FFormatArgumentData FormatArgumentDataEnum(uint8 Value, const FString &Name, const UObject *PinSubCategoryObject);
|
||||
};
|
||||
@@ -1,103 +0,0 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BlueprintErrors.h"
|
||||
#include "Containers/Array.h"
|
||||
#include "CoreMinimal.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "HAL/Platform.h"
|
||||
#include "Internationalization/Text.h"
|
||||
#include "K2Node.h"
|
||||
#include "UObject/NameTypes.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "UObject/UObjectGlobals.h"
|
||||
#include "BlueprintErrors.h"
|
||||
|
||||
#include "FormatError.generated.h"
|
||||
|
||||
class FBlueprintActionDatabaseRegistrar;
|
||||
class FString;
|
||||
class UEdGraph;
|
||||
class UObject;
|
||||
|
||||
//
|
||||
// FormatMessage and FormatErrorMessage
|
||||
//
|
||||
// This file defines two K2Nodes: FormatMessage, and FormatErrorMessage. The
|
||||
// only difference between them is that the former outputs the message as an
|
||||
// output pin. The latter outputs the message to the log instead.
|
||||
//
|
||||
// To implement code reuse, we put all the code into FormatMessage, and made
|
||||
// FormatErrorMessage a derived class of FormatMessage. The derived class
|
||||
// doesn't override anything - all it does is set a flag, the flag changes
|
||||
// the behavior of FormatMessage.
|
||||
//
|
||||
//
|
||||
UCLASS(MinimalAPI)
|
||||
class UK2Node_FormatMessage : public UK2Node
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
//~ Begin UObject Interface
|
||||
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||
//~ End UObject Interface
|
||||
|
||||
//~ Begin UEdGraphNode Interface.
|
||||
virtual void AllocateDefaultPins() override;
|
||||
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
|
||||
virtual bool ShouldShowNodeProperties() const override { return true; }
|
||||
virtual void PinConnectionListChanged(UEdGraphPin* Pin) override;
|
||||
virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override;
|
||||
virtual void PinTypeChanged(UEdGraphPin* Pin) override;
|
||||
virtual FText GetTooltipText() const override;
|
||||
virtual FText GetPinDisplayName(const UEdGraphPin* Pin) const override;
|
||||
//~ End UEdGraphNode Interface.
|
||||
|
||||
//~ Begin UK2Node Interface.
|
||||
virtual bool IsNodePure() const override { return false; }
|
||||
virtual void PostReconstructNode() override;
|
||||
virtual bool NodeCausesStructuralBlueprintChange() const override { return true; }
|
||||
virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
|
||||
virtual ERedirectType DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const override;
|
||||
virtual bool IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const override;
|
||||
virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
|
||||
virtual FText GetMenuCategory() const override;
|
||||
virtual int32 GetNodeRefreshPriority() const override { return EBaseNodeRefreshPriority::Low_UsesDependentWildcard; }
|
||||
//~ End UK2Node Interface.
|
||||
|
||||
protected:
|
||||
/** Create all necessary pins */
|
||||
void CreateCorrectPins();
|
||||
|
||||
/** Synchronize the type of the given argument pin with the type its connected to, or reset it to a wildcard pin if there's no connection */
|
||||
void SynchronizeArgumentPinType(UEdGraphPin* Pin);
|
||||
|
||||
/** Our derived class will set this to true, altering the behavior of this K2Node. **/
|
||||
virtual bool IsFormatErrorMessage() const { return false; }
|
||||
|
||||
protected:
|
||||
/** When adding arguments to the node, their names are placed here and are generated as pins during construction */
|
||||
UPROPERTY()
|
||||
TArray<FString> PinNames;
|
||||
|
||||
/** Tooltip text for this node. */
|
||||
FText NodeTooltip;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// This derives from FormatMessage.
|
||||
//
|
||||
UCLASS(MinimalAPI)
|
||||
class UK2Node_FormatErrorMessage : public UK2Node_FormatMessage
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
// Setting this flag alters the behavior of FormatMessage, making it
|
||||
// output to the log instead of to a pin.
|
||||
//
|
||||
virtual bool IsFormatErrorMessage() const override { return true; }
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "FormatError.h"
|
||||
#include "FormatMessage.h"
|
||||
|
||||
#include "Internationalization/TextFormatter.h"
|
||||
#include "BlueprintActionDatabaseRegistrar.h"
|
||||
#include "BlueprintNodeSpawner.h"
|
||||
#include "Containers/EnumAsByte.h"
|
||||
@@ -107,7 +108,7 @@ void UK2Node_FormatMessage::CreateCorrectPins()
|
||||
if (IsFormatErrorMessage())
|
||||
{
|
||||
if (FindPin(VerbosityPinName, EGPD_Input) == nullptr) {
|
||||
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum<ElxLogVerbosity>(), VerbosityPinName);
|
||||
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum<ElxFormatLogVerbosity>(), VerbosityPinName);
|
||||
P->DefaultValue = TEXT("Error");
|
||||
P->AutogeneratedDefaultValue = P->DefaultValue;
|
||||
}
|
||||
@@ -301,7 +302,7 @@ UFunction *ToFormatArgumentData(const UEdGraphSchema_K2 *Schema, const FEdGraphP
|
||||
//
|
||||
if (PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard && AllowWild)
|
||||
{
|
||||
return UlxBlueprintErrorLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxBlueprintErrorLibrary, FormatArgumentDataBlank));
|
||||
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataBlank));
|
||||
}
|
||||
|
||||
// Try to find a match in the UlxFormatDataLibrary.
|
||||
@@ -309,7 +310,7 @@ UFunction *ToFormatArgumentData(const UEdGraphSchema_K2 *Schema, const FEdGraphP
|
||||
for (auto It = TFieldIterator<UFunction>(UlxFormatDataLibrary::StaticClass()); It; ++It)
|
||||
{
|
||||
UFunction* Function = *It;
|
||||
FProperty* ValueProperty = Function->FindPropertyByName(TEXT("Value"));
|
||||
FProperty* ValueProperty = Function->FindPropertyByName(TEXT("AutoConvertedValue"));
|
||||
FEdGraphPinType ValuePinType;
|
||||
bool Convertible = Schema->ConvertPropertyToPinType(ValueProperty, ValuePinType);
|
||||
if (!Convertible) continue;
|
||||
@@ -322,7 +323,7 @@ UFunction *ToFormatArgumentData(const UEdGraphSchema_K2 *Schema, const FEdGraphP
|
||||
//
|
||||
if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Byte) && (nullptr != Cast<const UEnum>(PinType.PinSubCategoryObject)))
|
||||
{
|
||||
return UlxBlueprintErrorLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxBlueprintErrorLibrary, FormatArgumentDataEnum));
|
||||
return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataEnum));
|
||||
}
|
||||
|
||||
// A case for subclasses of 'Object' which are not exactly 'Object'
|
||||
@@ -359,7 +360,7 @@ void UK2Node_FormatMessage::ExpandNode(class FKismetCompilerContext& CompilerCon
|
||||
UFunction *FormatFunction;
|
||||
if (IsFormatErrorMessage())
|
||||
{
|
||||
FormatFunction = UlxBlueprintErrorLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxBlueprintErrorLibrary, FormatErrorInternal));
|
||||
FormatFunction = UK2Node_FormatMessage::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UK2Node_FormatMessage, FormatLogMessageInternal));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -399,7 +400,7 @@ void UK2Node_FormatMessage::ExpandNode(class FKismetCompilerContext& CompilerCon
|
||||
ConvertNode->SetFromFunction(Converter);
|
||||
ConvertNode->AllocateDefaultPins();
|
||||
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(ConvertNode, this);
|
||||
UEdGraphPin *ValuePin = ConvertNode->FindPin(TEXT("Value"));
|
||||
UEdGraphPin *ValuePin = ConvertNode->FindPin(TEXT("AutoConvertedValue"));
|
||||
UEdGraphPin *NamePin = ConvertNode->FindPinChecked(TEXT("Name"));
|
||||
UEdGraphPin *SubCategoryObjectPin = ConvertNode->FindPin(TEXT("PinSubCategoryObject"));
|
||||
|
||||
@@ -559,7 +560,7 @@ UK2Node_FormatMessage::UK2Node_FormatMessage(const FObjectInitializer& ObjectIni
|
||||
);
|
||||
}
|
||||
|
||||
UK2Node_FormatErrorMessage::UK2Node_FormatErrorMessage(const FObjectInitializer& ObjectInitializer)
|
||||
UK2Node_FormatLogMessage::UK2Node_FormatLogMessage(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
NodeTooltip = LOCTEXT("NodeTooltip",
|
||||
@@ -572,4 +573,70 @@ UK2Node_FormatErrorMessage::UK2Node_FormatErrorMessage(const FObjectInitializer&
|
||||
);
|
||||
}
|
||||
|
||||
ELogVerbosity::Type UK2Node_FormatMessage::ConvertElxFormatLogVerbosity(ElxFormatLogVerbosity Verbosity) {
|
||||
switch (Verbosity) {
|
||||
case ElxFormatLogVerbosity::Error: return ELogVerbosity::Error;
|
||||
case ElxFormatLogVerbosity::Warning: return ELogVerbosity::Warning;
|
||||
case ElxFormatLogVerbosity::Display: return ELogVerbosity::Display;
|
||||
case ElxFormatLogVerbosity::Log: return ELogVerbosity::Log;
|
||||
case ElxFormatLogVerbosity::ThrottledDisplay: return ELogVerbosity::Display;
|
||||
case ElxFormatLogVerbosity::ThrottledLog: return ELogVerbosity::Log;
|
||||
case ElxFormatLogVerbosity::Verbose: return ELogVerbosity::Verbose;
|
||||
case ElxFormatLogVerbosity::VeryVerbose: return ELogVerbosity::VeryVerbose;
|
||||
case ElxFormatLogVerbosity::Fatal: return ELogVerbosity::Fatal;
|
||||
}
|
||||
}
|
||||
|
||||
void UK2Node_FormatMessage::FormatLogMessageInternal(UObject *Context, ElxFormatLogVerbosity Verbosity, const FString &InPattern, TArray<FFormatArgumentData> InArgs)
|
||||
{
|
||||
// For throttled verbosity levels, suppress repeated messages with the
|
||||
// same format pattern. We key on the blueprint name + format pattern,
|
||||
// and allow at most one message per second per key.
|
||||
//
|
||||
if (Verbosity == ElxFormatLogVerbosity::ThrottledDisplay || Verbosity == ElxFormatLogVerbosity::ThrottledLog)
|
||||
{
|
||||
static TMap<FString, double> LastLogTime;
|
||||
double Now = FPlatformTime::Seconds();
|
||||
FString Key = Context->GetClass()->GetName() + TEXT("::") + InPattern;
|
||||
double &Last = LastLogTime.FindOrAdd(Key, 0.0);
|
||||
if (Now - Last < 1.0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Last = Now;
|
||||
}
|
||||
|
||||
// Generate the formatted string.
|
||||
//
|
||||
FText InPatternText(FText::FromString(InPattern));
|
||||
FText Message = FTextFormatter::Format(MoveTemp(InPatternText), MoveTemp(InArgs), false, false);
|
||||
FString MessageString = Message.ToString();
|
||||
|
||||
// Get the blueprint name.
|
||||
//
|
||||
// Normally, the log function expects you to pass in a filename, and a log
|
||||
// category name. We use the blueprint name for both.
|
||||
//
|
||||
// Using the blueprint name as a log category name is not technically
|
||||
// correct. However, there is no correct way to create log categories
|
||||
// from inside of blueprints. Doing it this way at least produces a reasonable
|
||||
// message inside the log. What doesn't work correctly is the log message
|
||||
// suppression system. Ie, console commands like 'log <category> verbose'
|
||||
// don't have any effect here. The design of the log message suppression
|
||||
// system is such that there just is no reasonable way to hook into it from
|
||||
// inside of blueprints.
|
||||
//
|
||||
FString BlueprintNameString = Context->GetClass()->GetName();
|
||||
auto BlueprintNameAnsi = StringCast<ANSICHAR>(*BlueprintNameString);
|
||||
FLogCategoryName BlueprintNameLogCategory(Context->GetClass()->GetFName());
|
||||
|
||||
// Output to Log
|
||||
//
|
||||
ELogVerbosity::Type VerbosityValue = ConvertElxFormatLogVerbosity(Verbosity);
|
||||
if (VerbosityValue <= ELogVerbosity::COMPILED_IN_MINIMUM_VERBOSITY)
|
||||
{
|
||||
FMsg::Logf(BlueprintNameAnsi.Get(), 0, BlueprintNameLogCategory, VerbosityValue, TEXT("%s"), *MessageString);
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
174
Source/Integration/FormatMessage.h
Normal file
174
Source/Integration/FormatMessage.h
Normal file
@@ -0,0 +1,174 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FormatMessage.h
|
||||
//
|
||||
// Two K2Nodes: FormatMessage and FormatLogMessage.
|
||||
// FormatMessage outputs a formatted string as a pin.
|
||||
// FormatLogMessage outputs it to the log instead.
|
||||
//
|
||||
// FormatLogMessage is a derived class that just sets
|
||||
// a flag to alter the base class behavior.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BreakToDebugger.h"
|
||||
#include "FormatDataLibrary.h"
|
||||
#include "Containers/Array.h"
|
||||
#include "CoreMinimal.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "HAL/Platform.h"
|
||||
#include "Internationalization/Text.h"
|
||||
#include "K2Node.h"
|
||||
#include "UObject/NameTypes.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "UObject/UObjectGlobals.h"
|
||||
|
||||
#include "FormatMessage.generated.h"
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// ElxFormatLogVerbosity
|
||||
//
|
||||
// Controls the ELogVerbosity of the UE_LOG directive inside
|
||||
// FormatLogMessage. Also controls the throttling of log
|
||||
// messages.
|
||||
//
|
||||
// Fatal is deliberately placed at the end so that the
|
||||
// editor defaults to Error (value 0) when the dropdown is
|
||||
// uninitialized. The numeric values don't match
|
||||
// ELogVerbosity, so a conversion function is needed.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class ElxFormatLogVerbosity : uint8 {
|
||||
|
||||
/** Prints an error to the console and log file. The editor collects and reports errors. */
|
||||
Error,
|
||||
|
||||
/** Prints a warning to the console and log file. The editor collects and reports warnings. */
|
||||
Warning,
|
||||
|
||||
/** Prints a message to the console and log file. */
|
||||
Display,
|
||||
|
||||
/** Prints a message to the log file, but not to the console. */
|
||||
Log,
|
||||
|
||||
/** Like Display, but suppresses repeated messages with the same format pattern (at most once per second). */
|
||||
ThrottledDisplay,
|
||||
|
||||
/** Like Log, but suppresses repeated messages with the same format pattern (at most once per second). */
|
||||
ThrottledLog,
|
||||
|
||||
/** Prints a message to the log file only if Verbose logging is enabled for the given category. */
|
||||
Verbose,
|
||||
|
||||
/** Prints a message to the log file only if VeryVerbose logging is enabled. */
|
||||
VeryVerbose,
|
||||
|
||||
/** Prints a fatal error to the console and log file, then crashes (this crashes the editor too). */
|
||||
Fatal,
|
||||
};
|
||||
|
||||
class FBlueprintActionDatabaseRegistrar;
|
||||
class FString;
|
||||
class UEdGraph;
|
||||
class UObject;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// UK2Node_FormatMessage
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
UCLASS(MinimalAPI)
|
||||
class UK2Node_FormatMessage : public UK2Node
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
//~ Begin UObject Interface
|
||||
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||
//~ End UObject Interface
|
||||
|
||||
//~ Begin UEdGraphNode Interface.
|
||||
virtual void AllocateDefaultPins() override;
|
||||
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
|
||||
virtual bool ShouldShowNodeProperties() const override { return true; }
|
||||
virtual void PinConnectionListChanged(UEdGraphPin* Pin) override;
|
||||
virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override;
|
||||
virtual void PinTypeChanged(UEdGraphPin* Pin) override;
|
||||
virtual FText GetTooltipText() const override;
|
||||
virtual FText GetPinDisplayName(const UEdGraphPin* Pin) const override;
|
||||
//~ End UEdGraphNode Interface.
|
||||
|
||||
//~ Begin UK2Node Interface.
|
||||
virtual bool IsNodePure() const override { return false; }
|
||||
virtual void PostReconstructNode() override;
|
||||
virtual bool NodeCausesStructuralBlueprintChange() const override { return true; }
|
||||
virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
|
||||
virtual ERedirectType DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const override;
|
||||
virtual bool IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const override;
|
||||
virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
|
||||
virtual FText GetMenuCategory() const override;
|
||||
virtual int32 GetNodeRefreshPriority() const override { return EBaseNodeRefreshPriority::Low_UsesDependentWildcard; }
|
||||
//~ End UK2Node Interface.
|
||||
|
||||
protected:
|
||||
// Create all necessary pins.
|
||||
//
|
||||
void CreateCorrectPins();
|
||||
|
||||
// Synchronize the type of the given argument pin
|
||||
// with the type its connected to, or reset it to
|
||||
// a wildcard pin if there's no connection.
|
||||
//
|
||||
void SynchronizeArgumentPinType(UEdGraphPin* Pin);
|
||||
|
||||
// Derived class sets this to true, altering
|
||||
// the behavior of this K2Node.
|
||||
//
|
||||
virtual bool IsFormatErrorMessage() const { return false; }
|
||||
|
||||
// When IsFormatErrorMessage is true, the K2Node
|
||||
// macroexpands to call this function, which
|
||||
// formats the message and outputs it to the log.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, meta=(WorldContext = "Context", BlueprintInternalUseOnly = "true"))
|
||||
static void FormatLogMessageInternal(UObject *Context, ElxFormatLogVerbosity Verbosity, const FString &InPattern, TArray<FFormatArgumentData> InArgs);
|
||||
|
||||
private:
|
||||
static ELogVerbosity::Type ConvertElxFormatLogVerbosity(ElxFormatLogVerbosity Verbosity);
|
||||
|
||||
protected:
|
||||
// Argument names added to the node, generated as pins
|
||||
// during construction.
|
||||
//
|
||||
UPROPERTY()
|
||||
TArray<FString> PinNames;
|
||||
|
||||
// Tooltip text for this node.
|
||||
//
|
||||
FText NodeTooltip;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// UK2Node_FormatLogMessage
|
||||
//
|
||||
// Derives from FormatMessage. Sets a flag to make
|
||||
// the base class output to the log instead of to
|
||||
// a pin.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
UCLASS(MinimalAPI)
|
||||
class UK2Node_FormatLogMessage : public UK2Node_FormatMessage
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
virtual bool IsFormatErrorMessage() const override { return true; }
|
||||
};
|
||||
@@ -1,6 +1,63 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Integration.h"
|
||||
#include "Common.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, Integration, "Integration" );
|
||||
#if WITH_EDITOR
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "UObject/SavePackage.h"
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "BlueprintExporter.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#endif
|
||||
|
||||
IMPLEMENT_PRIMARY_GAME_MODULE(FlxIntegrationModuleImpl, Integration, "Integration");
|
||||
|
||||
void FlxIntegrationModuleImpl::StartupModule()
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
OnAssetSavedHandle = UPackage::PackageSavedWithContextEvent.AddRaw(
|
||||
this, &FlxIntegrationModuleImpl::OnAssetSaved);
|
||||
#endif
|
||||
}
|
||||
|
||||
void FlxIntegrationModuleImpl::ShutdownModule()
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
UPackage::PackageSavedWithContextEvent.Remove(OnAssetSavedHandle);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void FlxIntegrationModuleImpl::OnAssetSaved(const FString& PackageFilename, UPackage* Package, FObjectPostSaveContext Context)
|
||||
{
|
||||
if (!Package) return;
|
||||
|
||||
ForEachObjectWithPackage(Package, [&](UObject* Object)
|
||||
{
|
||||
if (UBlueprint* BP = Cast<UBlueprint>(Object))
|
||||
{
|
||||
FString BPDir = FPaths::ProjectDir() / TEXT("Saved") / TEXT("BlueprintExports") / BP->GetName();
|
||||
|
||||
IFileManager::Get().DeleteDirectory(*BPDir, false, true);
|
||||
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
{
|
||||
FlxBlueprintExporter Exporter(Graph);
|
||||
|
||||
FString FilePath = BPDir / Graph->GetName() + TEXT(".txt");
|
||||
FString DetailsPath = BPDir / TEXT("DETAILS") / Graph->GetName() + TEXT(".txt");
|
||||
FFileHelper::SaveStringToFile(Exporter.GetOutput(), *FilePath);
|
||||
FFileHelper::SaveStringToFile(Exporter.GetDetails(), *DetailsPath);
|
||||
UE_LOG(LogLuprexIntegration, Warning, TEXT("Blueprint export: %s"), *FilePath);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3,4 +3,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleInterface.h"
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
|
||||
class FlxIntegrationModuleImpl : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
private:
|
||||
#if WITH_EDITOR
|
||||
void OnAssetSaved(const FString& PackageFilename, UPackage* Package, FObjectPostSaveContext Context);
|
||||
FDelegateHandle OnAssetSavedHandle;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -83,3 +83,42 @@ StringViewVec FlxLockedWrapper::GetAnimationQueues(IdView ids) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ElxLuaSyntaxCheck FlxLockedWrapper::ValidateLuaExpr(const FString &Code, FString &ErrorMessage) {
|
||||
FTCHARToUTF8 UCode(*Code);
|
||||
uint32_t retpklen;
|
||||
const char *retpk;
|
||||
Lockable.Wrapper.play_access(Get(), AccessKind::VALIDATE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk);
|
||||
ErrorMessage = FString(retpklen, (const UTF8CHAR*)retpk);
|
||||
if (ErrorMessage.IsEmpty())
|
||||
return ElxLuaSyntaxCheck::ValidLua;
|
||||
if (ErrorMessage == TEXT("slash command"))
|
||||
return ElxLuaSyntaxCheck::SlashCommand;
|
||||
if (ErrorMessage == TEXT("white space"))
|
||||
return ElxLuaSyntaxCheck::Whitespace;
|
||||
if (ErrorMessage == TEXT("truncated lua"))
|
||||
return ElxLuaSyntaxCheck::TruncatedLua;
|
||||
return ElxLuaSyntaxCheck::InvalidLua;
|
||||
}
|
||||
|
||||
void FlxLockedWrapper::ProbeLuaFunction(std::string_view datapk, int64 place_id, TFunction<void(std::string_view)> OnResult) {
|
||||
if (place_id == 0) place_id = GetActor();
|
||||
uint32_t retpklen;
|
||||
const char *retpk;
|
||||
Lockable.Wrapper.play_access(Get(), AccessKind::PROBE_LUA_CALL, place_id, datapk.size(), datapk.data(), &retpklen, &retpk);
|
||||
OnResult(std::string_view(retpk, retpklen));
|
||||
}
|
||||
|
||||
void FlxLockedWrapper::InvokeLuaFunction(std::string_view datapk, int64 place_id) {
|
||||
if (place_id == 0) place_id = GetActor();
|
||||
uint32_t retpklen;
|
||||
const char *retpk;
|
||||
Lockable.Wrapper.play_access(Get(), AccessKind::INVOKE_LUA_CALL, place_id, datapk.size(), datapk.data(), &retpklen, &retpk);
|
||||
}
|
||||
|
||||
void FlxLockedWrapper::InvokeLuaExpr(const FString &Code) {
|
||||
FTCHARToUTF8 UCode(*Code);
|
||||
uint32_t retpklen;
|
||||
const char *retpk;
|
||||
Lockable.Wrapper.play_access(Get(), AccessKind::INVOKE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,34 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// LockedWrapper.h
|
||||
//
|
||||
// Mutex-guarded access to the EngineWrapper.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "lpx-enginewrapper.hpp"
|
||||
#include "Common.h"
|
||||
|
||||
|
||||
// Class FlxLockableWrapper
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FlxLockableWrapper
|
||||
//
|
||||
// Contains the EngineWrapper and a Mutex to lock it.
|
||||
// This class has no methods. To access the EngineWrapper,
|
||||
// construct a FlxLockedWrapper, and then dereference it
|
||||
// using operator right arrow.
|
||||
// This class has no methods. To access the
|
||||
// EngineWrapper, construct a FlxLockedWrapper.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
class FlxLockableWrapper {
|
||||
private:
|
||||
FCriticalSection Mutex;
|
||||
EngineWrapper Wrapper;
|
||||
|
||||
// Temporary buffers. These are only used
|
||||
// inside wrapper methods. There's nothing
|
||||
// persistent in these.
|
||||
// Temporary buffers used only inside wrapper
|
||||
// methods. Nothing persistent in these.
|
||||
//
|
||||
TArray<uint32> AQLenBuffer;
|
||||
TArray<const char*> AQStrBuffer;
|
||||
@@ -28,50 +37,63 @@ public:
|
||||
friend class FlxLockedWrapper;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FlxLockedWrapper
|
||||
//
|
||||
// RAII lock guard. The constructor claims the mutex,
|
||||
// the destructor releases it. Use operator-> to
|
||||
// access the EngineWrapper.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
class FlxLockedWrapper {
|
||||
private:
|
||||
FlxLockableWrapper& Lockable;
|
||||
|
||||
// This function is called by luprex to output debugging
|
||||
// messages. It is a thin wrapper around UE_LOG.
|
||||
// Called by luprex to output debugging messages.
|
||||
// A thin wrapper around UE_LOG.
|
||||
//
|
||||
static void DPrintHook(const char *Msg, size_t Size);
|
||||
|
||||
public:
|
||||
// Import these types into our Namespace.
|
||||
// Import these types into our namespace.
|
||||
//
|
||||
using IdArray = LpxCommonTypes::IdArray;
|
||||
using IdView = LpxCommonTypes::IdView;
|
||||
using StringViewVec = LpxCommonTypes::StringViewVec;
|
||||
|
||||
|
||||
public:
|
||||
// The constructor of the FlxLockedWrapper claims the mutex.
|
||||
// The constructor claims the mutex.
|
||||
//
|
||||
FlxLockedWrapper(FlxLockableWrapper& w) : Lockable(w) {
|
||||
Lockable.Mutex.Lock();
|
||||
}
|
||||
|
||||
// The destructor of the FlxLockedWrapper releases the mutex.
|
||||
// The destructor releases the mutex.
|
||||
//
|
||||
~FlxLockedWrapper() {
|
||||
Lockable.Mutex.Unlock();
|
||||
}
|
||||
|
||||
// Operator right arrow accesses the EngineWrapper.
|
||||
// Operator-> accesses the EngineWrapper.
|
||||
//
|
||||
EngineWrapper* operator ->() {
|
||||
return &Lockable.Wrapper;
|
||||
}
|
||||
|
||||
// Get a pointer to the enginewrapper. This is not
|
||||
// very safe because you could keep the pointer after
|
||||
// Get a pointer to the EngineWrapper. Not very
|
||||
// safe because you could keep the pointer after
|
||||
// the LockedWrapper is destroyed. Don't do that.
|
||||
//
|
||||
EngineWrapper* Get() {
|
||||
return &Lockable.Wrapper;
|
||||
}
|
||||
|
||||
// Initialize the engine wrapper if it's not already.
|
||||
//
|
||||
// All this does is open the DLL and hook up all
|
||||
// the function pointers in the wrapper to point into
|
||||
// the DLL.
|
||||
// Initialize the engine wrapper if it's not
|
||||
// already. All this does is open the DLL and
|
||||
// hook up all the function pointers in the
|
||||
// wrapper to point into the DLL.
|
||||
//
|
||||
void InitWrapper();
|
||||
|
||||
@@ -85,9 +107,9 @@ public:
|
||||
|
||||
// Get the list of tangibles near the actor.
|
||||
//
|
||||
// This function is fast but not free. You should fetch this
|
||||
// once per frame and then store the IdView somewhere (like
|
||||
// in the TangibleManager, for example).
|
||||
// This function is fast but not free. You should
|
||||
// fetch this once per frame and then store the
|
||||
// IdView somewhere (like in the TangibleManager).
|
||||
//
|
||||
IdView GetNear(int64 id, double rx, double ry, double rz);
|
||||
|
||||
@@ -97,4 +119,28 @@ public:
|
||||
// next time you call this.
|
||||
//
|
||||
StringViewVec GetAnimationQueues(IdView ids);
|
||||
|
||||
// Call a Lua function. The datapk contains the
|
||||
// serialized class name, function name, and
|
||||
// arguments. If place_id is 0, defaults to the
|
||||
// current actor. The OnResult callback receives
|
||||
// the raw return data while the lock is held;
|
||||
// use it to copy the data before it goes away.
|
||||
//
|
||||
void ProbeLuaFunction(std::string_view datapk, int64 place_id, TFunction<void(std::string_view)> OnResult);
|
||||
|
||||
// Invoke a Lua function (fire-and-forget, no
|
||||
// return values).
|
||||
//
|
||||
void InvokeLuaFunction(std::string_view datapk, int64 place_id);
|
||||
|
||||
// Validate a Lua expression. Returns a syntax
|
||||
// classification and an error message. The error
|
||||
// message is empty if the code is valid.
|
||||
//
|
||||
ElxLuaSyntaxCheck ValidateLuaExpr(const FString &Code, FString &ErrorMessage);
|
||||
|
||||
// Execute a Lua expression.
|
||||
//
|
||||
void InvokeLuaExpr(const FString &Code);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
#include "LuaCall.h"
|
||||
#include "LuprexGameModeBase.h"
|
||||
#include "Tangible.h"
|
||||
#include "StringDecoder.h"
|
||||
|
||||
#include "EdGraphSchema_K2.h"
|
||||
@@ -196,51 +197,30 @@ FString UlxLuaCallLibrary::AllFunctionsWithPrefix(const TCHAR *Prefix)
|
||||
return Result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// General Lua-Callable functions of the Lua Call Library.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
void UlxLuaCallLibrary::ValidateLuaExpr(
|
||||
ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code)
|
||||
{
|
||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
|
||||
ErrorMessage = mode->ValidateLuaExpr(Code);
|
||||
if (ErrorMessage.IsEmpty())
|
||||
{
|
||||
Status = ElxLuaSyntaxCheck::ValidLua;
|
||||
}
|
||||
else if (ErrorMessage == TEXT("slash command"))
|
||||
{
|
||||
Status = ElxLuaSyntaxCheck::SlashCommand;
|
||||
}
|
||||
else if (ErrorMessage == TEXT("white space"))
|
||||
{
|
||||
Status = ElxLuaSyntaxCheck::Whitespace;
|
||||
}
|
||||
else if (ErrorMessage == TEXT("truncated lua"))
|
||||
{
|
||||
Status = ElxLuaSyntaxCheck::TruncatedLua;
|
||||
}
|
||||
else
|
||||
{
|
||||
Status = ElxLuaSyntaxCheck::InvalidLua;
|
||||
}
|
||||
}
|
||||
|
||||
void UlxLuaCallLibrary::InvokeLuaExpr(UObject *context, const FString &Code)
|
||||
{
|
||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
|
||||
mode->InvokeLuaExpr(Code);
|
||||
FlxLockedWrapper w(mode->GetLockableWrapper());
|
||||
w.InvokeLuaExpr(Code);
|
||||
}
|
||||
|
||||
// Resolve an AActor to a tangible place_id.
|
||||
// Returns 0 if place is null (meaning "use current
|
||||
// actor"), or the tangible ID if found. Returns -1
|
||||
// if place is non-null but has no tangible.
|
||||
//
|
||||
static int64 ResolvePlaceId(AActor *place)
|
||||
{
|
||||
if (place == nullptr) return 0;
|
||||
UlxTangible *tan = UlxTangible::GetActorTangibleOrLog(place);
|
||||
return tan ? tan->TangibleId : -1;
|
||||
}
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, const FString &fname)
|
||||
{
|
||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
mode->LuaCallBegin();
|
||||
FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
|
||||
sb.clear();
|
||||
sb.write_string(cname);
|
||||
sb.write_string(fname);
|
||||
}
|
||||
@@ -248,19 +228,33 @@ void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, con
|
||||
void UlxLuaCallLibrary::LuaCallInvoke(UObject *context, AActor *place)
|
||||
{
|
||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
|
||||
if (NotInitialized(sb)) return;
|
||||
mode->LuaCallEnd(AccessKind::INVOKE_LUA_CALL, place);
|
||||
int64 place_id = ResolvePlaceId(place);
|
||||
if (place_id < 0) { sb.clear(); return; }
|
||||
FlxLockedWrapper w(mode->GetLockableWrapper());
|
||||
w.InvokeLuaFunction(sb.view(), place_id);
|
||||
sb.clear();
|
||||
}
|
||||
|
||||
|
||||
bool UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place, UlxLuaValues *&ReturnArray)
|
||||
{
|
||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
|
||||
if (NotInitialized(sb)) return false;
|
||||
ReturnArray = mode->LuaCallEnd(AccessKind::PROBE_LUA_CALL, place);
|
||||
if ((ReturnArray == nullptr) || (ReturnArray->Length() < 1))
|
||||
int64 place_id = ResolvePlaceId(place);
|
||||
if (place_id < 0) {
|
||||
sb.clear();
|
||||
ReturnArray = NewObject<UlxLuaValues>(mode);
|
||||
return false;
|
||||
}
|
||||
ReturnArray = NewObject<UlxLuaValues>(mode);
|
||||
FlxLockedWrapper w(mode->GetLockableWrapper());
|
||||
w.ProbeLuaFunction(sb.view(), place_id, [&](std::string_view retpk) {
|
||||
ReturnArray->Initialize(retpk);
|
||||
});
|
||||
sb.clear();
|
||||
if (ReturnArray->Length() < 1)
|
||||
{
|
||||
UE_LOG(LogLuprexIntegration, Error, TEXT("corruption in lua_probe"));
|
||||
ReturnArray = nullptr;
|
||||
@@ -292,7 +286,7 @@ bool UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place, UlxLuaValu
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallArgument_string(UObject *context, const FString &pstring) {
|
||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
|
||||
if (NotInitialized(sb)) return;
|
||||
sb.write_simple_dynamic_tag(LuaValueType::STRING);
|
||||
sb.write_string(pstring);
|
||||
@@ -307,7 +301,7 @@ void UlxLuaCallLibrary::LuaCallArgument_name(UObject *context, const FName &pnam
|
||||
UE_LOG(LogBlueprint, Error, TEXT("Names passed to lua must be short, and must contain only lowercase and digits"));
|
||||
}
|
||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
|
||||
if (NotInitialized(sb)) return;
|
||||
sb.write_simple_dynamic_tag(LuaValueType::TOKEN);
|
||||
sb.write_string(namestr);
|
||||
@@ -315,7 +309,7 @@ void UlxLuaCallLibrary::LuaCallArgument_name(UObject *context, const FName &pnam
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallArgument_float(UObject *context, double pfloat) {
|
||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
|
||||
if (NotInitialized(sb)) return;
|
||||
sb.write_simple_dynamic_tag(LuaValueType::NUMBER);
|
||||
sb.write_double(pfloat);
|
||||
@@ -323,7 +317,7 @@ void UlxLuaCallLibrary::LuaCallArgument_float(UObject *context, double pfloat) {
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallArgument_int(UObject *context, int value) {
|
||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
|
||||
if (NotInitialized(sb)) return;
|
||||
sb.write_simple_dynamic_tag(LuaValueType::NUMBER);
|
||||
sb.write_double(value);
|
||||
@@ -331,7 +325,7 @@ void UlxLuaCallLibrary::LuaCallArgument_int(UObject *context, int value) {
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallArgument_vector(UObject *context, const FVector &pvector) {
|
||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
|
||||
if (NotInitialized(sb)) return;
|
||||
sb.write_simple_dynamic_tag(LuaValueType::VECTOR);
|
||||
sb.write_fvector(pvector);
|
||||
@@ -339,7 +333,7 @@ void UlxLuaCallLibrary::LuaCallArgument_vector(UObject *context, const FVector &
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallArgument_vector2d(UObject *context, const FVector2D &pvector) {
|
||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
|
||||
if (NotInitialized(sb)) return;
|
||||
sb.write_simple_dynamic_tag(LuaValueType::VECTOR);
|
||||
sb.write_double(pvector.X);
|
||||
@@ -349,7 +343,7 @@ void UlxLuaCallLibrary::LuaCallArgument_vector2d(UObject *context, const FVector
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallArgument_boolean(UObject *context, bool pbool) {
|
||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
|
||||
if (NotInitialized(sb)) return;
|
||||
sb.write_simple_dynamic_tag(LuaValueType::BOOLEAN);
|
||||
sb.write_bool(pbool);
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// LuaCall.h
|
||||
//
|
||||
// Low-level functions for calling Lua from blueprints.
|
||||
// The "Call Lua" K2Node macroexpands into sequences
|
||||
// of these functions.
|
||||
//
|
||||
// Also contains UlxLuaValues, which stores return
|
||||
// values from Lua calls, and FlxParsedProto, which
|
||||
// parses Lua function prototype strings.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
@@ -9,46 +23,20 @@
|
||||
|
||||
class UlxLuaValues;
|
||||
|
||||
// Classify lua code syntactically:
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SlashCommand: starts with a slash, therefore, not lua
|
||||
// WhitespaceOnly: the input only contains whitespace
|
||||
// ValidSyntax: the input is valid lua code
|
||||
// TruncatedCode: the input is truncated
|
||||
// InvalidSyntax: invalid lua
|
||||
// FlxParsedProto
|
||||
//
|
||||
UENUM(BlueprintType)
|
||||
enum class ElxLuaSyntaxCheck : uint8 {
|
||||
SlashCommand,
|
||||
Whitespace,
|
||||
ValidLua,
|
||||
TruncatedLua,
|
||||
InvalidLua,
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// The first argument to LuaCallNode is a function
|
||||
// prototype of the form:
|
||||
//
|
||||
// These are the types that can actually be packed into
|
||||
// a serialized buffer.
|
||||
// class.name(int arg1, int arg2) : int ret1
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Return values can be omitted. The last return
|
||||
// value can be an ellipsis. The class name can be
|
||||
// asterisk.
|
||||
//
|
||||
// This is a little parser that parses Lua function 'prototypes'.
|
||||
// The prototypes look like this:
|
||||
//
|
||||
// class.name(int arg1, int arg2) : int ret1, int ret2
|
||||
//
|
||||
// The return values can be omitted if the function has no return
|
||||
// values. Optionally, there can be one last return value which
|
||||
// is just an ellipsis. The class name can be asterisk.
|
||||
//
|
||||
// For more information about the meaning of all this, see the docs
|
||||
// for the 'Call Lua' blueprint node.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
class FlxParsedProto
|
||||
{
|
||||
@@ -60,21 +48,33 @@ public:
|
||||
Pin(const FString &T, const FString &N) : Type(T), Name(N) {}
|
||||
};
|
||||
|
||||
// If parsing generated an error, the error
|
||||
// message is stored here.
|
||||
//
|
||||
FString ErrorMessage;
|
||||
TArray<FString> Tokens;
|
||||
int NextToken;
|
||||
|
||||
// The results of parsing the prototype.
|
||||
//
|
||||
FString ClassName;
|
||||
FString FunctionName;
|
||||
TArray<Pin> Arguments;
|
||||
TArray<Pin> ReturnValues;
|
||||
bool ExtraReturnValues;
|
||||
|
||||
// Used internally to help generate the error
|
||||
// message.
|
||||
//
|
||||
TArray<FString> Tokens;
|
||||
int NextToken;
|
||||
|
||||
private:
|
||||
// Check the next token to see if it's exactly equal to text.
|
||||
// Check the next token to see if it's exactly
|
||||
// equal to text.
|
||||
//
|
||||
bool IsLiteral(const TCHAR *text);
|
||||
|
||||
// Check the next token to see if it's an identifier.
|
||||
// Check the next token to see if it's an
|
||||
// identifier.
|
||||
//
|
||||
bool IsIdent();
|
||||
|
||||
@@ -96,26 +96,24 @@ public:
|
||||
FlxParsedProto(const FString &str) { Parse(str); }
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// To make a Lua Call from inside of a blueprint, use the convenient "Call Lua"
|
||||
// blueprint node. This node macroexpands into a sequence of low-level
|
||||
// function calls. This library contains the low-level functions that
|
||||
// "Call Lua" macroexpands into.
|
||||
// UlxLuaCallLibrary
|
||||
//
|
||||
// The procedure for making a lua call using the low-level functions is as follows:
|
||||
// Low-level functions that LuaCallNode macroexpands
|
||||
// into. Every LuaCallNode expands into the following
|
||||
// sequence of functions:
|
||||
//
|
||||
// * Use LuaCallBegin to put the class name and function name into the argument buffer.
|
||||
// * Use LuaCallArgumentXXX to put arguments into the argument buffer.
|
||||
// * Use LuaCallInvoke or LuaCallProbe to actually make the call.
|
||||
// * Use LuaCallReturnValueXXX to fetch return values from the return buffer.
|
||||
// * LuaCallBegin to set class/function name.
|
||||
// * LuaCallArgument_XXX to pack arguments.
|
||||
// * LuaCallInvoke or LuaCallProbe to call.
|
||||
// * LuaCallReturnValue_XXX to fetch results.
|
||||
//
|
||||
// The two buffers are basically global variables, they are part of the
|
||||
// LuprexGameModeBase. This is okay because blueprint is single-threaded.
|
||||
// The buffers used by these functions are global
|
||||
// variables in LuprexGameModeBase. It is ok to use
|
||||
// globals, because blueprint is single-threaded.
|
||||
//
|
||||
// The following three libraries contain all the low-level functions.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
UCLASS()
|
||||
class INTEGRATION_API UlxLuaCallLibrary : public UObject
|
||||
@@ -123,24 +121,25 @@ class INTEGRATION_API UlxLuaCallLibrary : public UObject
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Get an argument packing function or a return value unpacking function.
|
||||
// Get an argument packing function or a return
|
||||
// value unpacking function.
|
||||
//
|
||||
static UFunction *GetArgumentPacker(const FString &Type);
|
||||
static UFunction *GetReturnValueUnpacker(const FString &Type);
|
||||
|
||||
// Get the types supported for arguments and return values, as a documentation string.
|
||||
// Get the types supported for arguments and
|
||||
// return values, as a documentation string.
|
||||
//
|
||||
static FString AllFunctionsWithPrefix(const TCHAR *Prefix);
|
||||
static FString AllKnownArgumentTypes() { return AllFunctionsWithPrefix(TEXT("LuaCallArgument_")); }
|
||||
static FString AllKnownReturnValueTypes() { return AllFunctionsWithPrefix(TEXT("LuaCallReturnValue_")); }
|
||||
|
||||
public:
|
||||
// Syntactically validate lua code. Parses the code and
|
||||
// returns an error message. If the code is error-free, the
|
||||
// error message is the empty string.
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
|
||||
static void ValidateLuaExpr(ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code);
|
||||
// Functions that initiate and complete the lua call.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
|
||||
static void InvokeLuaExpr(UObject *context, const FString &Code);
|
||||
@@ -154,9 +153,11 @@ public:
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", ExpandBoolAsExecs="ReturnValue", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
|
||||
static bool LuaCallProbe(UObject *context, AActor *Place, UlxLuaValues *&ReturnArray);
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// Functions that pack arguments into the call buffer.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
|
||||
static void LuaCallArgument_string(UObject *context, const FString &Value);
|
||||
@@ -180,14 +181,15 @@ public:
|
||||
static void LuaCallArgument_boolean(UObject *context, bool Value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This class stores an array of values that were returned by Lua.
|
||||
// UlxLuaValues
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Stores an array of values returned by Lua.
|
||||
// Provides a cursor-based API for reading values
|
||||
// one at a time with type checking.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class INTEGRATION_API UlxLuaValues : public UObject
|
||||
@@ -203,7 +205,8 @@ private:
|
||||
//
|
||||
TArray<ElxLuaValueType> Types;
|
||||
|
||||
// For each chunk, a pointer to the serialized data.
|
||||
// For each chunk, a pointer to the serialized
|
||||
// data.
|
||||
//
|
||||
TArray<std::string_view> Data;
|
||||
|
||||
@@ -216,15 +219,17 @@ private:
|
||||
//
|
||||
void Empty();
|
||||
|
||||
// Compare two types. If they aren't equal, possibly log an error, and return a status code.
|
||||
// Compare two types. If they aren't equal,
|
||||
// possibly log an error, and return a status
|
||||
// code.
|
||||
//
|
||||
static ElxSuccessOrWrongType CheckType(bool LogErrorOnWrongType, ElxLuaValueType Type, ElxLuaValueType Desired);
|
||||
|
||||
public:
|
||||
UlxLuaValues() { Cursor = 0; }
|
||||
|
||||
// Copies the data, and then preprocesses it to make sure
|
||||
// it's all valid. If it's not valid, returns false.
|
||||
// Copies the data, then preprocesses it to make
|
||||
// sure it's all valid. Returns false if invalid.
|
||||
//
|
||||
bool Initialize(std::string_view data);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "LuaCallNode.h"
|
||||
#include "StringDecoder.h"
|
||||
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "BlueprintActionDatabaseRegistrar.h"
|
||||
#include "BlueprintNodeSpawner.h"
|
||||
#include "Containers/EnumAsByte.h"
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BlueprintErrors.h"
|
||||
#include "Containers/Array.h"
|
||||
#include "CoreMinimal.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
@@ -13,7 +11,6 @@
|
||||
#include "UObject/NameTypes.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "UObject/UObjectGlobals.h"
|
||||
#include "BlueprintErrors.h"
|
||||
|
||||
#include "LuaCallNode.generated.h"
|
||||
|
||||
|
||||
@@ -187,58 +187,6 @@ void ALuprexGameModeBase::UpdatePossessedTangible() {
|
||||
}
|
||||
}
|
||||
|
||||
UlxLuaValues *ALuprexGameModeBase::LuaCallEnd(AccessKind kind, int64 place_id) {
|
||||
std::string_view datapk = LuaCallBuffer.view();
|
||||
FlxLockedWrapper w(LockableWrapper);
|
||||
if (place_id == 0) place_id = w.GetActor();
|
||||
uint32_t retpklen;
|
||||
const char *retpk;
|
||||
w->play_access(w.Get(), kind, place_id, datapk.size(), datapk.data(), &retpklen, &retpk);
|
||||
if (kind == AccessKind::PROBE_LUA_CALL)
|
||||
{
|
||||
UlxLuaValues *Result = NewObject<UlxLuaValues>(this);
|
||||
Result->Initialize(std::string_view(retpk, retpklen));
|
||||
return Result;
|
||||
}
|
||||
else return nullptr;
|
||||
}
|
||||
|
||||
UlxLuaValues *ALuprexGameModeBase::LuaCallEnd(AccessKind kind) {
|
||||
return LuaCallEnd(kind, int64(0));
|
||||
}
|
||||
|
||||
UlxLuaValues *ALuprexGameModeBase::LuaCallEnd(AccessKind kind, AActor *place) {
|
||||
if (place == nullptr) {
|
||||
return LuaCallEnd(kind, int64(0));
|
||||
} else {
|
||||
UlxTangible *tan = UlxTangible::GetActorTangibleOrLog(place);
|
||||
if (tan == nullptr) {
|
||||
return NewObject<UlxLuaValues>(this);
|
||||
} else {
|
||||
return LuaCallEnd(kind, tan->TangibleId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FString ALuprexGameModeBase::ValidateLuaExpr(const FString &Code)
|
||||
{
|
||||
FTCHARToUTF8 UCode(*Code);
|
||||
FlxLockedWrapper w(LockableWrapper);
|
||||
uint32_t retpklen;
|
||||
const char *retpk;
|
||||
w->play_access(w.Get(), AccessKind::VALIDATE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk);
|
||||
FString Result(retpklen, (const UTF8CHAR*)retpk);
|
||||
return Result;
|
||||
}
|
||||
|
||||
void ALuprexGameModeBase::InvokeLuaExpr(const FString &Code)
|
||||
{
|
||||
FTCHARToUTF8 UCode(*Code);
|
||||
FlxLockedWrapper w(LockableWrapper);
|
||||
uint32_t retpklen;
|
||||
const char *retpk;
|
||||
w->play_access(w.Get(), AccessKind::INVOKE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk);
|
||||
}
|
||||
|
||||
void ALuprexGameModeBase::OnWorldPreActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds)
|
||||
{
|
||||
@@ -336,7 +284,7 @@ void ALuprexGameModeBase::InitializeGlobalState()
|
||||
|
||||
// If somebody generates a log message that's severe enough, break to debugger.
|
||||
BreakToDebuggerLogVerbosityDevice.Reset(
|
||||
new FlxDebugBlueprintErrorsOutputDevice(BreakToDebuggerLogVerbosity));
|
||||
new FlxBreakToDebuggerOutputDevice(BreakToDebuggerLogVerbosity));
|
||||
}
|
||||
|
||||
void ALuprexGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "AssetLookup.h"
|
||||
#include "LuprexSockets.h"
|
||||
#include "TriggeredTask.h"
|
||||
#include "BlueprintErrors.h"
|
||||
#include "BreakToDebugger.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "Widgets/CommonActivatableWidgetContainer.h"
|
||||
#include "CommonActivatableWidget.h"
|
||||
@@ -82,38 +82,12 @@ public:
|
||||
void LookAtChanged();
|
||||
|
||||
|
||||
// Assemble a lua call. Note that this is the lowest-level interface.
|
||||
// These functions are wrapped by the functions in UlxLuaCallLibrary,
|
||||
// and those in turn are wrapped by the K2Node "LuaInvoke" and "LuaProbe".
|
||||
// The Lua Call Assembly Buffer. Used by
|
||||
// UlxLuaCallLibrary to build up a call across
|
||||
// multiple UFUNCTION invocations.
|
||||
//
|
||||
// At this level, the process of calling Lua is:
|
||||
//
|
||||
// * Use LuaCallBegin
|
||||
// * Get the lua call buffer:
|
||||
// - add a class name
|
||||
// - add a function name
|
||||
// - add function parameters
|
||||
// * Use LuaCallEnd.
|
||||
// * Process any return values in the UlxLuaValues array.
|
||||
//
|
||||
FlxStreamBuffer &LuaCallBegin() { LuaCallBuffer.clear(); return LuaCallBuffer; }
|
||||
FlxStreamBuffer &LuaCallGetBuffer() { return LuaCallBuffer; }
|
||||
UlxLuaValues *LuaCallEnd(AccessKind kind);
|
||||
UlxLuaValues *LuaCallEnd(AccessKind kind, int64 place_id);
|
||||
UlxLuaValues *LuaCallEnd(AccessKind kind, AActor *place);
|
||||
void LuaCallClear() { LuaCallBuffer.clear(); }
|
||||
FlxStreamBuffer &GetLuaCallBuffer() { return LuaCallBuffer; }
|
||||
|
||||
// Validate some lua code. Returns an error message.
|
||||
// If the lua is well-formed, the error message is the
|
||||
// empty string. The syntax of the code is checked using
|
||||
// an otherwise empty lua interpreter, so this is purely
|
||||
// a syntax check.
|
||||
//
|
||||
FString ValidateLuaExpr(const FString &Code);
|
||||
|
||||
// Invoke some lua code.
|
||||
//
|
||||
void InvokeLuaExpr(const FString &Code);
|
||||
|
||||
// Get the Asset Lookup table.
|
||||
const UlxAssetLookup *GetAssetLookup() const { return AssetLookup; }
|
||||
@@ -160,12 +134,17 @@ public:
|
||||
|
||||
// The sensitivity level at which a log message triggers a debugger breakpoint.
|
||||
UPROPERTY(EditAnywhere, Category="Debugging Tools")
|
||||
ElxLogVerbosity BreakToDebuggerLogVerbosity;
|
||||
ElxBreakToDebuggerThreshold BreakToDebuggerLogVerbosity;
|
||||
|
||||
// The Luprex EngineWrapper, with a Mutex to protect it.
|
||||
// To access it, construct a FlxLockedWrapper.
|
||||
//
|
||||
FlxLockableWrapper LockableWrapper;
|
||||
|
||||
// Get the LockableWrapper.
|
||||
//
|
||||
FlxLockableWrapper& GetLockableWrapper() { return LockableWrapper; }
|
||||
|
||||
// The Lua Call Assembly Buffer.
|
||||
FlxStreamBuffer LuaCallBuffer;
|
||||
|
||||
@@ -195,5 +174,5 @@ public:
|
||||
FDelegateHandle OnWorldPostActorTickHandle;
|
||||
|
||||
// The device that implements BreakToDebuggerLogVerbosity, above.
|
||||
TUniquePtr<FlxDebugBlueprintErrorsOutputDevice> BreakToDebuggerLogVerbosityDevice;
|
||||
TUniquePtr<FlxBreakToDebuggerOutputDevice> BreakToDebuggerLogVerbosityDevice;
|
||||
};
|
||||
|
||||
64
Source/Integration/MovementComponentState.cpp
Normal file
64
Source/Integration/MovementComponentState.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "MovementComponentState.h"
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "Tangible.h"
|
||||
|
||||
FlxMovementComponentState::FlxMovementComponentState(UCharacterMovementComponent *CMC)
|
||||
{
|
||||
Velocity = CMC->Velocity;
|
||||
bIsAccelerating = !CMC->GetCurrentAcceleration().IsNearlyZero();
|
||||
MaxWalkSpeed = CMC->MaxWalkSpeed;
|
||||
MovementMode = CMC->MovementMode;
|
||||
bIsFalling = CMC->IsFalling();
|
||||
bIsCrouching = CMC->IsCrouching();
|
||||
}
|
||||
|
||||
FString UlxMovementComponentStateLibrary::DebugString(const FlxMovementComponentState &State)
|
||||
{
|
||||
const UEnum *ModeEnum = StaticEnum<EMovementMode>();
|
||||
FString ModeName = ModeEnum ? ModeEnum->GetNameStringByValue(State.MovementMode.GetValue()) : FString::FromInt(State.MovementMode.GetValue());
|
||||
ModeName.RemoveFromStart(TEXT("MOVE_"));
|
||||
return FString::Printf(TEXT("Vel=(%.1f, %.1f, %.1f) MaxWalk=%.1f Mode=%s Accel=%s Fall=%s Crouch=%s"),
|
||||
State.Velocity.X, State.Velocity.Y, State.Velocity.Z,
|
||||
State.MaxWalkSpeed,
|
||||
*ModeName,
|
||||
State.bIsAccelerating ? TEXT("true") : TEXT("false"),
|
||||
State.bIsFalling ? TEXT("true") : TEXT("false"),
|
||||
State.bIsCrouching ? TEXT("true") : TEXT("false"));
|
||||
}
|
||||
|
||||
bool UlxMovementComponentStateLibrary::GetShouldMove(const FlxMovementComponentState &State)
|
||||
{
|
||||
return State.bIsAccelerating && State.Velocity.Size2D() >= 3.0;
|
||||
}
|
||||
|
||||
FlxMovementComponentState UlxMovementComponentStateLibrary::GetMovementComponentState(UAnimInstance *AnimInstance)
|
||||
{
|
||||
if (!AnimInstance) return FlxMovementComponentState();
|
||||
|
||||
AActor *Actor = AnimInstance->GetOwningActor();
|
||||
if (!Actor) return FlxMovementComponentState();
|
||||
|
||||
UCharacterMovementComponent *CMC = Actor->FindComponentByClass<UCharacterMovementComponent>();
|
||||
if (CMC && CMC->MovementMode != MOVE_None) return FlxMovementComponentState(CMC);
|
||||
|
||||
UlxTangible *Tangible = UlxTangible::GetActorTangibleQuiet(Actor);
|
||||
if (Tangible) return Tangible->FakeMovementComponentState;
|
||||
|
||||
if (CMC) return FlxMovementComponentState(CMC);
|
||||
return FlxMovementComponentState();
|
||||
}
|
||||
|
||||
FlxMovementComponentState UlxMovementComponentStateLibrary::SetFakeMovementComponentState(AActor *Actor, const FlxMovementComponentState &State)
|
||||
{
|
||||
if (!Actor) return State;
|
||||
UlxTangible *Tangible = UlxTangible::GetActorTangibleOrLog(Actor);
|
||||
if (Tangible)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("SetFakeMovementComponentState(%s): %s"), *Actor->GetName(), *DebugString(State));
|
||||
Tangible->FakeMovementComponentState = State;
|
||||
}
|
||||
return State;
|
||||
}
|
||||
129
Source/Integration/MovementComponentState.h
Normal file
129
Source/Integration/MovementComponentState.h
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "MovementComponentState.generated.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Very often, the animation blueprint of a character will
|
||||
// want to know the state of the character movement
|
||||
// component, including such things as Velocity,
|
||||
// Acceleration, IsFalling, etc. However, the movement
|
||||
// component cannot be accessed directly from the
|
||||
// animation graph, since it runs on a worker thread.
|
||||
// By copying the data into this plain struct, the
|
||||
// animation graph can safely read it.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct INTEGRATION_API FlxMovementComponentState
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Luprex|Movement Component State")
|
||||
FVector Velocity = FVector::ZeroVector;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Luprex|Movement Component State")
|
||||
bool bIsAccelerating = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Luprex|Movement Component State")
|
||||
float MaxWalkSpeed = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Luprex|Movement Component State")
|
||||
TEnumAsByte<EMovementMode> MovementMode = MOVE_None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Luprex|Movement Component State")
|
||||
bool bIsFalling = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Luprex|Movement Component State")
|
||||
bool bIsCrouching = false;
|
||||
|
||||
FlxMovementComponentState() = default;
|
||||
explicit FlxMovementComponentState(class UCharacterMovementComponent *CMC);
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class INTEGRATION_API UlxMovementComponentStateLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
///////////////////////////////////////////////////////////
|
||||
//
|
||||
// True field getters.
|
||||
//
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Movement Component State", meta = (BlueprintThreadSafe))
|
||||
static FVector GetVelocity(const FlxMovementComponentState &State) { return State.Velocity; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Movement Component State", meta = (BlueprintThreadSafe))
|
||||
static bool GetIsAccelerating(const FlxMovementComponentState &State) { return State.bIsAccelerating; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Movement Component State", meta = (BlueprintThreadSafe))
|
||||
static float GetMaxWalkSpeed(const FlxMovementComponentState &State) { return State.MaxWalkSpeed; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Movement Component State", meta = (BlueprintThreadSafe))
|
||||
static EMovementMode GetMovementMode(const FlxMovementComponentState &State) { return State.MovementMode; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Movement Component State", meta = (BlueprintThreadSafe))
|
||||
static bool GetIsFalling(const FlxMovementComponentState &State) { return State.bIsFalling; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Movement Component State", meta = (BlueprintThreadSafe))
|
||||
static bool GetIsCrouching(const FlxMovementComponentState &State) { return State.bIsCrouching; }
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
//
|
||||
// These are not true getters, but actually calculate
|
||||
// simple values from the existing fields.
|
||||
//
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Movement Component State", meta = (BlueprintThreadSafe))
|
||||
static float GetGroundSpeed(const FlxMovementComponentState &State) { return State.Velocity.Size2D(); }
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Movement Component State", meta = (BlueprintThreadSafe))
|
||||
static bool GetShouldMove(const FlxMovementComponentState &State);
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
//
|
||||
// Debugging.
|
||||
//
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Movement Component State")
|
||||
static FString DebugString(const FlxMovementComponentState &State);
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
//
|
||||
// Other operations.
|
||||
//
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
// Get a snapshot the Movement Component State.
|
||||
//
|
||||
// Normally, this just pulls data directly from the
|
||||
// character movement component. However, we sometimes
|
||||
// need to disable the movement component, especially
|
||||
// during cutscenes. We provide an alternative 'Fake
|
||||
// Movement Component State' stored on the actor's
|
||||
// UlxTangible. When the movement component's mode is
|
||||
// MOVE_None, this function returns the fake movement
|
||||
// component state instead. The fake movement component
|
||||
// state can be updated manually in blueprints.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, Category = "Luprex|Movement Component State", meta = (DefaultToSelf = "AnimInstance"))
|
||||
static FlxMovementComponentState GetMovementComponentState(UAnimInstance *AnimInstance);
|
||||
|
||||
// Update the fake movement component state stored on
|
||||
// the actor's tangible. The fake movement component
|
||||
// state is usually read by the animation blueprint
|
||||
// when the real movement component is disabled.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, Category = "Luprex|Movement Component State", meta = (DefaultToSelf = "Actor", AutoCreateRefTerm = "State"))
|
||||
static FlxMovementComponentState SetFakeMovementComponentState(AActor *Actor, const FlxMovementComponentState &State);
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SampleActorComponent.h"
|
||||
|
||||
// Sets default values for this component's properties
|
||||
USampleActorComponent::USampleActorComponent()
|
||||
{
|
||||
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
|
||||
// off to improve performance if you don't need them.
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
|
||||
// Called when the game starts
|
||||
void USampleActorComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// ...
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Called every frame
|
||||
void USampleActorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "SampleActorComponent.generated.h"
|
||||
|
||||
|
||||
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
|
||||
class INTEGRATION_API USampleActorComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Sets default values for this component's properties
|
||||
USampleActorComponent();
|
||||
|
||||
protected:
|
||||
// Called when the game starts
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
public:
|
||||
// Called every frame
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
|
||||
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SampleEmptyClass.h"
|
||||
|
||||
SampleEmptyClass::SampleEmptyClass()
|
||||
{
|
||||
}
|
||||
|
||||
SampleEmptyClass::~SampleEmptyClass()
|
||||
{
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class INTEGRATION_API SampleEmptyClass
|
||||
{
|
||||
public:
|
||||
SampleEmptyClass();
|
||||
~SampleEmptyClass();
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SampleUObject.h"
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "SampleUObject.generated.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS()
|
||||
class INTEGRATION_API USampleUObject : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
};
|
||||
@@ -234,7 +234,7 @@ class INTEGRATION_API UlxScriptedAnimationLibrary : public UBlueprintFunctionLib
|
||||
public:
|
||||
// Get all the major 'World Clocks' in a single struct.
|
||||
//
|
||||
UFUNCTION(BlueprintPure, Category = "Utilities|Time", meta=(WorldContext = "WorldContextObject"))
|
||||
UFUNCTION(BlueprintCallable, Category = "Utilities|Time", meta=(WorldContext = "WorldContextObject"))
|
||||
static FlxWorldClocks GetAllWorldClocks(const UObject *WorldContextObject);
|
||||
|
||||
// Get the data to drive Sequence Evaluators and Multi Blend
|
||||
|
||||
@@ -253,7 +253,7 @@ void UlxTangible::FinishedAnimation(AActor *target, const FlxAnimationStep &step
|
||||
tan->AnimTracker.FinishedAnimation(step.Hash);
|
||||
if (AutoUpdate) tan->AutoUpdatePosition();
|
||||
FString DebugString = UlxAnimationStepLibrary::AnimationStepDebugString(step);
|
||||
UE_LOG(LogLuprex, Display, TEXT("Animation Finished: %s"), *DebugString);
|
||||
// UE_LOG(LogLuprex, Display, TEXT("FinishedAnimation: %s"), *DebugString);
|
||||
}
|
||||
|
||||
bool UlxTangible::AnimationStepIsFinished(AActor *target, const FlxAnimationStep &step)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "AnimQueue.h"
|
||||
#include "MovementComponentState.h"
|
||||
#include "ScriptedAnimation.h"
|
||||
#include "Tangible.generated.h"
|
||||
|
||||
@@ -55,10 +56,14 @@ public:
|
||||
UPROPERTY()
|
||||
FString ActorBlueprintName;
|
||||
|
||||
// This is
|
||||
// Every tangible can store a set of scripted animations.
|
||||
UPROPERTY()
|
||||
UlxScriptedAnimations *ScriptedAnimations = nullptr;
|
||||
|
||||
// Every tangible can store a fake movement component state.
|
||||
UPROPERTY()
|
||||
FlxMovementComponentState FakeMovementComponentState;
|
||||
|
||||
// Animation tracker
|
||||
FlxAnimTracker AnimTracker;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
#include "UtilityLibrary.h"
|
||||
#include "LuprexGameModeBase.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "Kismet/KismetSystemLibrary.h"
|
||||
@@ -239,3 +240,23 @@ FKey UlxUtilityLibrary::GetKeyByNameString(const FString &Name)
|
||||
FKey Key = FKey(FName(*Name));
|
||||
return Key.IsValid() ? Key : FKey();
|
||||
}
|
||||
|
||||
FVector UlxUtilityLibrary::GetActorForwardVelocity(const AActor *Actor, double Speed, bool bSnapToXY)
|
||||
{
|
||||
if (!Actor) return FVector::ZeroVector;
|
||||
FVector Forward = Actor->GetActorForwardVector();
|
||||
if (bSnapToXY)
|
||||
{
|
||||
Forward.Z = 0.0;
|
||||
if (!Forward.Normalize()) return FVector::ZeroVector;
|
||||
}
|
||||
return Forward * Speed;
|
||||
}
|
||||
|
||||
void UlxUtilityLibrary::ValidateLuaExpr(
|
||||
ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code)
|
||||
{
|
||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
|
||||
FlxLockedWrapper w(mode->GetLockableWrapper());
|
||||
Status = w.ValidateLuaExpr(Code, ErrorMessage);
|
||||
}
|
||||
|
||||
@@ -158,4 +158,18 @@ public:
|
||||
//
|
||||
UFUNCTION(BlueprintPure, Category = "Input|Key")
|
||||
static FKey GetKeyByNameString(const FString &Name);
|
||||
|
||||
// Get the actor's forward vector multiplied by a speed.
|
||||
// If SnapToXY is true, the forward vector is projected
|
||||
// onto the XY plane and renormalized before scaling.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Utility", meta = (DefaultToSelf = "Actor"))
|
||||
static FVector GetActorForwardVelocity(const AActor *Actor, double Speed = 1.0, bool bSnapToXY = false);
|
||||
|
||||
// Syntactically validate lua code. Parses the
|
||||
// code and returns an error message. If the code
|
||||
// is error-free, the error message is empty.
|
||||
//
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Utility")
|
||||
static void ValidateLuaExpr(ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code);
|
||||
};
|
||||
|
||||
42
build.py
42
build.py
@@ -144,7 +144,7 @@ def get_build_mode_from_command_line():
|
||||
"""
|
||||
mode = sys.argv[1].lower() if len(sys.argv) > 1 else 'all'
|
||||
if mode in ["cpp", "cxx"]: mode = "c++"
|
||||
if not mode in ["all", "c++", "clean", "ccjson"]:
|
||||
if not mode in ["all", "c++", "clean", "ccjson", "code-workspace"]:
|
||||
sys.exit(f"Invalid build mode: {mode}")
|
||||
return mode
|
||||
|
||||
@@ -337,30 +337,52 @@ def build_intellisense_database_for_clangd():
|
||||
rsp = cpp_to_rsp[cpp]
|
||||
args = [clang, "@"+rsp]
|
||||
ccjson.append({ "file" : cpp, "arguments":args, "directory":ccdir })
|
||||
Path(f"{INTEGRATION}/.vscode").mkdir(exist_ok=True)
|
||||
Path(f"{INTEGRATION}/.vscode/compile_commands.json").write_text(json.dumps(ccjson, indent=2))
|
||||
|
||||
|
||||
def run_generateprojectfiles_in_sandbox():
|
||||
"""
|
||||
Unreal's GenerateProjectFiles does an absolutely terrible
|
||||
job of generating project files for vscode. It's so bad
|
||||
that we've decided to just not use it at all. But we
|
||||
still sometimes want to look at the output, to see what
|
||||
it would have generated. We run GenerateProjectFiles
|
||||
in a sandbox so it can't modify the real project. Then,
|
||||
we leave the output in the sandbox for inspection. The
|
||||
results don't affect our build system at all.
|
||||
"""
|
||||
sandbox = Path(f"{INTEGRATION}/GPF-output")
|
||||
if sandbox.exists():
|
||||
shutil.rmtree(sandbox)
|
||||
sandbox.mkdir()
|
||||
(sandbox / "Integration.uproject").write_bytes(Path(f"{INTEGRATION}/Integration.uproject").read_bytes())
|
||||
for name in ["Source", "Config", "Content"]:
|
||||
(sandbox / name).symlink_to(f"../{name}")
|
||||
shell(str(sandbox), f'{UNREALENGINE}/GenerateProjectFiles.{BAT} -projectfiles -project="{sandbox}/Integration.uproject" -game')
|
||||
# Remove the symlinks and uproject copy, leaving only generated files
|
||||
for name in ["Source", "Config", "Content", "Integration.uproject"]:
|
||||
(sandbox / name).unlink()
|
||||
|
||||
|
||||
def generate_integration_code_workspace():
|
||||
"""
|
||||
We build Integration.code-workspace from a template that we
|
||||
wrote ourselves, Integration.code-workspace.tpl.json.
|
||||
We use UnrealBuildTool to generate Integration.code-workspace.ubt,
|
||||
but we don't use it: we just keep it as a reference that you can
|
||||
refer to when editing the template.
|
||||
"""
|
||||
workspace = f"{INTEGRATION}/Integration.code-workspace"
|
||||
workspace_ubt = f"{INTEGRATION}/Integration.code-workspace.ubt"
|
||||
template = f"{INTEGRATION}/Integration.code-workspace.tpl.json"
|
||||
Path(workspace).unlink(missing_ok=True)
|
||||
Path(workspace_ubt).unlink(missing_ok=True)
|
||||
shell(INTEGRATION, f'{UNREALENGINE}/GenerateProjectFiles.{BAT} -projectfiles -project="{INTEGRATION}/Integration.uproject" -game')
|
||||
Path(workspace).rename(workspace_ubt)
|
||||
expand_json_file(template, workspace, CONFIG)
|
||||
|
||||
|
||||
def build_clean():
|
||||
"""
|
||||
This code is underdeveloped.
|
||||
|
||||
For a more aggressive form of cleaning, use 'git clean -xfd',
|
||||
which resets your git repository to its pristine state.
|
||||
DANGER: this deletes any new source code you've created!
|
||||
"""
|
||||
shell(f"{INTEGRATION}/luprex", "make clean")
|
||||
shell(INTEGRATION, f"{UNREALENGINE}/Engine/Build/BatchFiles/{BUILD_BAT} -waitmutex IntegrationEditor {OS} {DEBUG} {INTEGRATION}/Integration.uproject -clean")
|
||||
@@ -378,12 +400,16 @@ os.chdir(f"{INTEGRATION}/EnginePatches")
|
||||
if MODE == "ccjson":
|
||||
build_intellisense_database_for_clangd()
|
||||
|
||||
if MODE == "code-workspace":
|
||||
generate_integration_code_workspace()
|
||||
|
||||
if MODE == "all":
|
||||
unzip_unreal_engine_and_apply_patch()
|
||||
generate_buildconfiguration_xml()
|
||||
generate_lpx_paths()
|
||||
generate_integration_uproject()
|
||||
run_unrealengine_setup_bat_replacement()
|
||||
run_generateprojectfiles_in_sandbox()
|
||||
build_unrealbuildtool()
|
||||
generate_integration_code_workspace()
|
||||
|
||||
|
||||
@@ -1227,20 +1227,6 @@ LUA_API int lua_nkeys (lua_State *L, int idx) {
|
||||
return n;
|
||||
}
|
||||
|
||||
LUA_API int lua_nthkey (lua_State *L, int idx, int n) {
|
||||
StkId t;
|
||||
lua_lock(L);
|
||||
t = index2addr(L, idx);
|
||||
api_check(L, ttistable(t), "table expected");
|
||||
api_incr_top(L);
|
||||
api_incr_top(L);
|
||||
int res = luaH_nthkey(L, hvalue(t), n, L->top - 2);
|
||||
if (res == 0)
|
||||
L->top -= 2;
|
||||
lua_unlock(L);
|
||||
return res;
|
||||
}
|
||||
|
||||
LUA_API void lua_concat (lua_State *L, int n) {
|
||||
lua_lock(L);
|
||||
api_checknelems(L, n);
|
||||
|
||||
@@ -163,27 +163,6 @@ int luaH_nkeys (Table *t) {
|
||||
return t->nnkeys;
|
||||
}
|
||||
|
||||
int luaH_nthkey (lua_State *L, Table *t, int n, StkId pair) {
|
||||
n -= 1; /* convert to C indexing */
|
||||
if ((n < 0) || (n >= t->nnkeys)) {
|
||||
setnilvalue(pair+0);
|
||||
setnilvalue(pair+1);
|
||||
return 0;
|
||||
}
|
||||
int index = t->sequence[n];
|
||||
if (index < t->sizearray) {
|
||||
setnvalue(pair + 0, index + 1);
|
||||
setobj2s(L, pair + 1, &t->array[index].i_val);
|
||||
return 1;
|
||||
} else {
|
||||
index -= t->sizearray;
|
||||
Node *n = t->node + index;
|
||||
setobj2s(L, pair + 0, gkey(n));
|
||||
setobj2s(L, pair + 1, gval(n));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int successorindex (lua_State *L, Table *t, StkId key) {
|
||||
int i, seqno;
|
||||
if (ttisnil(key)) {
|
||||
@@ -706,32 +685,11 @@ void luaH_setint (lua_State *L, Table *t, int key, TValue *value) {
|
||||
}
|
||||
|
||||
/*
|
||||
** Try to find a boundary in table `t'. A `boundary' is an integer index
|
||||
** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
|
||||
** Return the number of keys in the table.
|
||||
*/
|
||||
int luaH_getn (Table *t) {
|
||||
unsigned int j = 1;
|
||||
unsigned int i = 0;
|
||||
/* find `i' and `j' such that i is present and j is not */
|
||||
while (!ttisnil(luaH_getint(t, j))) {
|
||||
i = j;
|
||||
j *= 2;
|
||||
if (j > cast(unsigned int, MAX_INT)) { /* overflow? */
|
||||
/* table was built with bad purposes: resort to linear search */
|
||||
i = 1;
|
||||
while (!ttisnil(luaH_getint(t, i))) i++;
|
||||
return i - 1;
|
||||
return t->nnkeys;
|
||||
}
|
||||
}
|
||||
/* now do a binary search between them */
|
||||
while (j - i > 1) {
|
||||
unsigned int m = (i+j)/2;
|
||||
if (ttisnil(luaH_getint(t, m))) j = m;
|
||||
else i = m;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if defined(LUA_DEBUG)
|
||||
|
||||
@@ -37,7 +37,6 @@ LUAI_FUNC void luaH_resize (lua_State *L, Table *t, int nasize, int nhsize);
|
||||
LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, int nasize);
|
||||
LUAI_FUNC void luaH_free (lua_State *L, Table *t);
|
||||
LUAI_FUNC int luaH_nkeys (Table *t);
|
||||
LUAI_FUNC int luaH_nthkey (lua_State *L, Table *t, int n, StkId pair);
|
||||
LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key);
|
||||
LUAI_FUNC int luaH_getn (Table *t);
|
||||
|
||||
|
||||
@@ -317,7 +317,6 @@ LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud);
|
||||
LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);
|
||||
|
||||
LUA_API int (lua_nkeys) (lua_State *L, int idx);
|
||||
LUA_API int (lua_nthkey) (lua_State *L, int idx, int n);
|
||||
|
||||
/*
|
||||
** ===============================================================
|
||||
|
||||
@@ -28,6 +28,11 @@ function engio.move(action, xyz, facing)
|
||||
tangible.animate{tan=actor, anim={action=action, interactive=true, xyz=xyz, facing=facing}}
|
||||
end
|
||||
|
||||
function moveto(x, y)
|
||||
local z = tangible.animfinal(actor).xyz[3]
|
||||
tangible.animate{tan=actor, anim={action="moveto", xyz={x, y, z}, facing=math.auto}}
|
||||
end
|
||||
|
||||
function cube.lookhotkeys(keys)
|
||||
keys:add("Z", "Cube Hi", function () dprint("Doing Cube Hi") end)
|
||||
keys:add("X", "Cube Bye", function () dprint("Doing Cube Bye") end)
|
||||
|
||||
Reference in New Issue
Block a user