Allow marking methods as executable by method call tracks in the editor.#119435
Allow marking methods as executable by method call tracks in the editor.#119435HEX-23 wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Since _blend_process() runs every frame, the overhead of checking for flags is too high. At the very least, you should have a flag in TrackCacheMethod indicating whether it can be executed, so that it can be cached in _update_caches().
struct TrackCacheMethod : public TrackCache {
#ifdef TOOLS_ENABLED
Vector<StringName> callable_methods;
#endif
TrackCacheMethod() { type = Animation::TYPE_METHOD; }
};I’ll leave it to the gdscript team to decide whether this is acceptable from a security standpoint and whether it’s an appropriate way to collect methods annotated from scripts.
It does not actually process each frame. The perf overhead here is quite marginal because:
If we wish to pursue absolute minimal overhead, I can unfold the original |
|
Retrieving the If there is a way to retrieve the flag without retrieving reference and running a loop, you could use that method; means having a list of available methods by animation preview on the GDScript side. However, I consider it less than ideal from an architectural standpoint to have GDScript handle this for the sake of animation, as it would result in a tighter coupling between the animation and GDScript. Otherwise, as mentioned above, you will need to cache the flag into TrackCache. However, if it is difficult to notify the cache of changes made to the object, you may need to choose the former method. |
|
I assume this should be considered first: whether this should be managed on a per-method basis. Additionally, there's the consideration that this annotation is only valid for custom scripts, so if you want to apply it to the built-in functions of the base class, a wrapper function is required everytime. Another point of concern is that when For example, if we were to manage this at the class level rather than the method level, one possible implementation might be to allow @tool(enum ToolFlag)enum ToolFlag {
TOOL_FLAG_NONE = 0,
TOOL_FLAG_ALLOW_CALL_METHOD_BY_ANIMATION = 1,
} |
cd02c35 to
7cf24dd
Compare
|
Added a cache. All runtime overhead is now a single random access of |
I am not sure which one seems better: Marking the whole class is perhaps more convenient and requires less intrusive modifications to method level logic. But method-level approach offers extra flexibility and makes it easier to track down and isolate the function being invoked (especially when something went wrong). I am leaving this to reviewers.
Adding a way to mark built-in functions has its own complexities: when should these registration code be executed? Should there be some sort of dedicated scripts that does this? Or should it be arbitrary and "recommended to be put into an autoload"? And how should script functions be marked as such? The design about execution time of this can be a bit tricky. Marking the whole class could, in a way, circumvent this (by marking the whole script as safe to call in editor, all functions of the node with that script attached can be invoked by method call track in the editor). But, again, this is the aforementioned whole class vs individual method thing. Marking individual methods is more flexible and, imo, more secure because you have to explicitly enable the feature for methods that you intend to call, there's no chance to enable this feature for dangerous functions (like
Current implementation issues an error at compile time if |
|
It is necessary to have some method of notification to update the cache when a script is modified during animation. For example, when a script is modified or an object’s |
ec64d0b to
532903d
Compare
Addressed. There might be multiple animation players / trees in the edited scene, and they do not automatically become the actively edited one even if they are the only anim node in the scene. Hence I did a tree traversal instead of fetching the active player. Such traversal and cache invalidation is limited to at most once per frame (if requested multiple times in a row) and is editor-only, the performance impact should be minimal (w.r.t. the logic required). |
Ah, right. In that case, maybe you should place it in the |
532903d to
24cc7fd
Compare
Addressed. The logic is moved to |
|
For now, I think it's ready to be discussed as one approach from a coding perspective within the Animation area. However, as mentioned above, we need to consider whether to manage this on a per-method basis or a per-class basis. I assume you could likely implement this with less code if you manage it on a per-class basis, but we'll need to discuss this from a safety perspective. I'll bring this up as one of the topics for the next Animation meeting. Separately from animation, it must be reviewed by the gdscript team. While this PR adds new annotations and flags, but as mentioned above, an approach involving class-level management via |
|
In my opinion, From a security perspective, nothing will change. You can already encounter code executing in the editor when opening a project if you have Also, as TokageItLab noted, the To be fair, this option has one drawback. The editor likely won't be able to distinguish whether a method is executable in the editor (built-in, belonging to a normal extension class, or Finally, I think we should probably highlight executable keypoints on call method tracks with a different color, fill, or shape (similar to how |
From a convenience perspective, using a checkbox (a.k.a. a property of the keyframe) may be an easy way (possibly with a limit that non-tool script methods are never called). But it also means you need to explicitly specify that "I want this keyframe triggered in editor" every time you are keying. Whereas a method based approach allows one to mark any "visual-only" methods as allowed to be executed and forget about it when creating and editing animations. The practicality of method call tracks is two-fold: for one thing they can be used to trigger gameplay events that are synced to animation, for another they can be used to drive more sophisticated visual logic. The feature being discussed here is clearly focusing on the latter use case. I feel it more natural to implement gameplay and visual logic as separate functions, and automatically allow all visual logics to be played in the editor, whereas blocking any gameplay events that could possibly break things. TokageItLab explained in PR#92990 the reason why they disagree with the checkbox approach, and think that a code-side, "which functions are allowed" registration is preferred. This PR is also an attempt to do this the code-side way (note that there's no checkbox thing in this PR, it is still a single-place permission, just at a difference place). We can also make a certain subset of built-in functions callable if using the design in this PR. It's only a matter of registration (we would also need to add compatibility functions since this affects their hashes / alternatively, we may also mask the |
Can't this be solved by copying and pasting keypoints? Also, in my experience, the number of keypoints in call method tracks is usually small compared to property animation tracks. Alternatively, we could add a checkbox to the call method track, not to individual keypoints. If you want some methods to be called in the editor, but not others, you can create two tracks and check only one of them. |
Yes, technically this can be done. But I still prefer to just mark the functions I need once and for all. Since the definition is a one-time thing, but the invocations are numerous (especially when we have a lot of animations in the project).
From a convenience perspective this seems viable to me. Yet some team members do not agree to adopt the checkbox approach, and demand explicit control from the callee side, as in the discussion here. For now I will leave this PR as-is, and see if any further consensus can be reached among devs. |
24cc7fd to
16662d0
Compare
Resolves proposal#9939. Implements an idea similar to PR#92990, but with a method-based, more explicit and easy-to-externally-disable approach.
Introduction
This PR allows marking individual methods as executable by method call tracks in the editor.
At the base level, this is implemented as an additional bit flag in the flags bitfield of method info. This is language-agnostic and could easily extend to native C++ modules, any scripting language, or GDExtension.
This particular PR also adds an annotation
@editor_allow_animation_callto the GDScript, which marks any annotated GDScript function to be executable by method call tracks in the editor. Implementation for C# and GDExtension can be done in follow-up PRs.Usage
In GDScript, add
@editor_allow_animation_callto any function would mark that function as allowed to be executed by method call tracks in the editor. When playing animation that contains function calls, only functions that are explicitly marked as such would be executed in the editor.This annotation can only be used in a
@toolscript. If not, an error is generated during compilation.Demo
For comparison, I use 2 methods:
regularis not annotated,allowedis annotated. Both triggers some particle effects, and are called in an animation. The results are as follows:The code:
The MRP: anim_method_call.zip
Security Concerns
According to the discussion in PR#92990, the main concern over such feature is that it (or more specifically the implementation of PR#92990) may allow arbitrary code execution simply by opening the project and cannot be easily detected and disabled from the outside. This PR addresses these issues by:
Thus it follows the suggestions in PR#92990 regarding security.
Potential Follow-Ups
Add support for more languages. This should only require a registration-time snippet that flips a certain bit (
METHOD_FLAG_EDITOR_ALLOW_ANIMATION_CALL) in the method info (along with some front-end works for scripting languages, ofc).