Removed _session from session backend to prevent a reference cycle, and because it didn't expose any especially essential functionality anyway. Deleted nonfunctioning method GDScriptInteractiveSession.forget because I don't know how I would get it to work and it would always have been kind of pointless even if it did work, especially now that there's no way to call it from within the session backend anyway. Had plugin.gd explicitly load gdscript_interactive_session.gd instead of directly referencing GDScriptInteractiveSession, to fix a rare and obscure bug that has only ever happened once, and, according to user TheDex on the Godot Discord server, was because of the way plugins are loaded.
This commit is contained in:
parent
42d36ee94b
commit
78fd86fbbc
99
README.md
99
README.md
|
@ -96,74 +96,48 @@ of the session script instance with `extends`, and give it a public name
|
|||
with `class_name`, though the utility of doing any of these things
|
||||
is questionable.
|
||||
|
||||
### Execution context
|
||||
### Accessible variables and functions
|
||||
|
||||
The plugin sends your code to an instance of class
|
||||
`GDScriptInteractiveSession`, which stores a dynamic script resource
|
||||
and an instance of that script. It uses your code to rebuild
|
||||
the script resource's source code, and then calls a method on the instance
|
||||
to run the code.
|
||||
|
||||
Therefore, in your code, `self` is the instance of the script
|
||||
generated by the instance of `GDScriptInteractiveSession`.
|
||||
|
||||
The `GDScriptInteractiveSession` instance injects a reference to itself
|
||||
into your `self`: you can access the `GDScriptInteractiveSession`
|
||||
as `_session`.
|
||||
|
||||
`_session` has three public methods: `eval`, `inject`, and `forget`.
|
||||
|
||||
`_session.eval` takes a `String` and evaluates it, in exactly the same manner
|
||||
as it evaluates code you input via the plugin.
|
||||
|
||||
`_session.inject` takes a `StringName` and a `Variant`, creates a variable
|
||||
by the given name in your `self`, and sets it to the given `Variant`.
|
||||
This is not very useful when called via the plugin, since you can just
|
||||
declare variables with `var` and set them with `=`.
|
||||
The purpose of `_session.inject` is to inject variables
|
||||
into an interactive session from outside it.
|
||||
|
||||
`_session.forget` takes a `String` containing a declaration stub,
|
||||
such as `var foo` or `func bar`. It is supposed to then erase the declaration
|
||||
from the session, but it currently does not work. For example,
|
||||
if you've previously entered `var foo = 'baz'`, subsequently entering
|
||||
`_session.forget("var foo")` should delete the variable, but will not.
|
||||
For now, if you need to discard the contents of a variable,
|
||||
I recommend setting the variable to null --
|
||||
though I realize this is not quite the same thing
|
||||
as deleting the variable from the class. Fortunately,
|
||||
the main reason you might ever want to delete a declaration
|
||||
would be to redeclare the symbol as something else,
|
||||
and you can already do that without deleting (see "Redeclaring symbols").
|
||||
|
||||
Besides `_session`, `self` also has access to the variable
|
||||
`_editor_interface`. This is not universal to all
|
||||
`GDScriptInteractiveSession`s; it's injected by the plugin
|
||||
after the session is constructed. `_editor_interface` is a reference
|
||||
to Godot's `EditorInterface`.
|
||||
You can do `_editor_interface.get_edited_scene_root()`
|
||||
`self` has the variable `_editor_interface`.
|
||||
You can use `_editor_interface.get_edited_scene_root()`
|
||||
to get the root node of the scene currently open in the editor.
|
||||
|
||||
Finally, the dynamically constructed function
|
||||
which `GDScriptInteractiveSession.eval` will call on your `self`
|
||||
is called `_recur`. The reason for this naming choice
|
||||
is to reflect how you might use `_recur()` from within an input code snippet:
|
||||
namely, since the function your code goes into is called `_recur`,
|
||||
that means any call to `_recur()` within your snippet
|
||||
will be a recursive call back to the beginning of the snippet.
|
||||
Granted, the utility of a recursive code snippet is questionable at best,
|
||||
as you cannot pass arguments to it.
|
||||
`self` also has the method `_recur`, which contains your input snippet,
|
||||
such that calling `_recur()` from within your input snippet
|
||||
will constitute a recursive call (for what limited use that may be,
|
||||
considering `_recur` accepts no arguments).
|
||||
|
||||
### Interactive GDScript in-game
|
||||
|
||||
As covered in the previous section, the plugin's evaluation backend
|
||||
is a class called `GDScriptInteractiveSession`.
|
||||
The instance `self` in the context of an input snippet is an instance
|
||||
of a dynamically generated and regenerated anonymous backend script
|
||||
maintained by a frontend instance of a helper class
|
||||
called `GDScriptInteractiveSession`.
|
||||
|
||||
Because this class is separate from the plugin, and only the plugin,
|
||||
not the helper class, relies on the editor, it is possible to instantiate
|
||||
`GDScriptInteractiveSession` is separate from the plugin entry point class.
|
||||
Only the plugin entry point class, not `GDScriptInteractiveSession`,
|
||||
relies on the editor, so it is possible to instantiate
|
||||
`GDScriptInteractiveSession` in-game. You can use this to easily create
|
||||
a cheat console or debug console. (If you want it to be at all useful,
|
||||
be sure to `inject` a reference to a `Node`.)
|
||||
a cheat console or debug console.
|
||||
|
||||
`GDScriptInteractiveSession` is instantiated
|
||||
with `GDScriptInteractiveSession.new()`. The resulting frontend instance
|
||||
has two public methods: `eval` and `inject`.
|
||||
|
||||
`eval` takes a string and evaluates it as GDScript code. If the code
|
||||
is an expression or contains a `return` statement, `eval` returns
|
||||
what the code returns; otherwise, `eval` returns null.
|
||||
`eval` is responsible for evaluating your input snippets
|
||||
in the in-editor GDScript input field,
|
||||
and therefore has exactly the same semantics.
|
||||
|
||||
`inject` takes a `StringName` and any other value, creates a variable
|
||||
by the given name in the session backend, and sets it to the given value.
|
||||
This allows you to provide a session with references it can't obtain
|
||||
with `eval` alone.
|
||||
|
||||
Note that the backend of a fresh instance of `GDScriptInteractiveSession`
|
||||
does not have `_editor_interface`; that is `inject`ed
|
||||
by the plugin entry point class.
|
||||
|
||||
### Known issues
|
||||
|
||||
|
@ -205,6 +179,3 @@ so I have no ideas as to any solution to this issue.
|
|||
In the meantime, you can work around the issue by following the expression up
|
||||
with `; pass`. `GDScriptInteractiveSession.eval` will never treat a snippet
|
||||
as an expression if it contains multiple statements.
|
||||
|
||||
Another issue: as previously mentioned,
|
||||
`_session.forget` currently does not work.
|
||||
|
|
|
@ -96,74 +96,48 @@ of the session script instance with `extends`, and give it a public name
|
|||
with `class_name`, though the utility of doing any of these things
|
||||
is questionable.
|
||||
|
||||
### Execution context
|
||||
### Accessible variables and functions
|
||||
|
||||
The plugin sends your code to an instance of class
|
||||
`GDScriptInteractiveSession`, which stores a dynamic script resource
|
||||
and an instance of that script. It uses your code to rebuild
|
||||
the script resource's source code, and then calls a method on the instance
|
||||
to run the code.
|
||||
|
||||
Therefore, in your code, `self` is the instance of the script
|
||||
generated by the instance of `GDScriptInteractiveSession`.
|
||||
|
||||
The `GDScriptInteractiveSession` instance injects a reference to itself
|
||||
into your `self`: you can access the `GDScriptInteractiveSession`
|
||||
as `_session`.
|
||||
|
||||
`_session` has three public methods: `eval`, `inject`, and `forget`.
|
||||
|
||||
`_session.eval` takes a `String` and evaluates it, in exactly the same manner
|
||||
as it evaluates code you input via the plugin.
|
||||
|
||||
`_session.inject` takes a `StringName` and a `Variant`, creates a variable
|
||||
by the given name in your `self`, and sets it to the given `Variant`.
|
||||
This is not very useful when called via the plugin, since you can just
|
||||
declare variables with `var` and set them with `=`.
|
||||
The purpose of `_session.inject` is to inject variables
|
||||
into an interactive session from outside it.
|
||||
|
||||
`_session.forget` takes a `String` containing a declaration stub,
|
||||
such as `var foo` or `func bar`. It is supposed to then erase the declaration
|
||||
from the session, but it currently does not work. For example,
|
||||
if you've previously entered `var foo = 'baz'`, subsequently entering
|
||||
`_session.forget("var foo")` should delete the variable, but will not.
|
||||
For now, if you need to discard the contents of a variable,
|
||||
I recommend setting the variable to null --
|
||||
though I realize this is not quite the same thing
|
||||
as deleting the variable from the class. Fortunately,
|
||||
the main reason you might ever want to delete a declaration
|
||||
would be to redeclare the symbol as something else,
|
||||
and you can already do that without deleting (see "Redeclaring symbols").
|
||||
|
||||
Besides `_session`, `self` also has access to the variable
|
||||
`_editor_interface`. This is not universal to all
|
||||
`GDScriptInteractiveSession`s; it's injected by the plugin
|
||||
after the session is constructed. `_editor_interface` is a reference
|
||||
to Godot's `EditorInterface`.
|
||||
You can do `_editor_interface.get_edited_scene_root()`
|
||||
`self` has the variable `_editor_interface`.
|
||||
You can use `_editor_interface.get_edited_scene_root()`
|
||||
to get the root node of the scene currently open in the editor.
|
||||
|
||||
Finally, the dynamically constructed function
|
||||
which `GDScriptInteractiveSession.eval` will call on your `self`
|
||||
is called `_recur`. The reason for this naming choice
|
||||
is to reflect how you might use `_recur()` from within an input code snippet:
|
||||
namely, since the function your code goes into is called `_recur`,
|
||||
that means any call to `_recur()` within your snippet
|
||||
will be a recursive call back to the beginning of the snippet.
|
||||
Granted, the utility of a recursive code snippet is questionable at best,
|
||||
as you cannot pass arguments to it.
|
||||
`self` also has the method `_recur`, which contains your input snippet,
|
||||
such that calling `_recur()` from within your input snippet
|
||||
will constitute a recursive call (for what limited use that may be,
|
||||
considering `_recur` accepts no arguments).
|
||||
|
||||
### Interactive GDScript in-game
|
||||
|
||||
As covered in the previous section, the plugin's evaluation backend
|
||||
is a class called `GDScriptInteractiveSession`.
|
||||
The instance `self` in the context of an input snippet is an instance
|
||||
of a dynamically generated and regenerated anonymous backend script
|
||||
maintained by a frontend instance of a helper class
|
||||
called `GDScriptInteractiveSession`.
|
||||
|
||||
Because this class is separate from the plugin, and only the plugin,
|
||||
not the helper class, relies on the editor, it is possible to instantiate
|
||||
`GDScriptInteractiveSession` is separate from the plugin entry point class.
|
||||
Only the plugin entry point class, not `GDScriptInteractiveSession`,
|
||||
relies on the editor, so it is possible to instantiate
|
||||
`GDScriptInteractiveSession` in-game. You can use this to easily create
|
||||
a cheat console or debug console. (If you want it to be at all useful,
|
||||
be sure to `inject` a reference to a `Node`.)
|
||||
a cheat console or debug console.
|
||||
|
||||
`GDScriptInteractiveSession` is instantiated
|
||||
with `GDScriptInteractiveSession.new()`. The resulting frontend instance
|
||||
has two public methods: `eval` and `inject`.
|
||||
|
||||
`eval` takes a string and evaluates it as GDScript code. If the code
|
||||
is an expression or contains a `return` statement, `eval` returns
|
||||
what the code returns; otherwise, `eval` returns null.
|
||||
`eval` is responsible for evaluating your input snippets
|
||||
in the in-editor GDScript input field,
|
||||
and therefore has exactly the same semantics.
|
||||
|
||||
`inject` takes a `StringName` and any other value, creates a variable
|
||||
by the given name in the session backend, and sets it to the given value.
|
||||
This allows you to provide a session with references it can't obtain
|
||||
with `eval` alone.
|
||||
|
||||
Note that the backend of a fresh instance of `GDScriptInteractiveSession`
|
||||
does not have `_editor_interface`; that is `inject`ed
|
||||
by the plugin entry point class.
|
||||
|
||||
### Known issues
|
||||
|
||||
|
@ -205,6 +179,3 @@ so I have no ideas as to any solution to this issue.
|
|||
In the meantime, you can work around the issue by following the expression up
|
||||
with `; pass`. `GDScriptInteractiveSession.eval` will never treat a snippet
|
||||
as an expression if it contains multiple statements.
|
||||
|
||||
Another issue: as previously mentioned,
|
||||
`_session.forget` currently does not work.
|
||||
|
|
|
@ -537,11 +537,6 @@ func inject(varname: StringName, varval: Variant) -> void:
|
|||
_script_instance.set(varname, varval)
|
||||
|
||||
|
||||
## Erases a declaration from the script instance. Takes effect next execute.
|
||||
func forget(declname: String) -> void:
|
||||
_decls.erase(declname)
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
if not _static_init_done:
|
||||
_static_init()
|
||||
|
@ -549,4 +544,3 @@ func _init() -> void:
|
|||
_script.source_code = ""
|
||||
_script.reload()
|
||||
_script_instance = _script.new()
|
||||
inject(&'_session', self)
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
name="GDScript Input Field Plugin"
|
||||
description="Provides an input field in the editor output panel for executing arbitrary GDScript in a REPL-like fashion."
|
||||
author="blujai831"
|
||||
version="0.4.2"
|
||||
version="0.5.0"
|
||||
script="plugin.gd"
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
extends EditorPlugin
|
||||
## Adds a GDScript input field to the EditorLog.
|
||||
|
||||
static var GDSIS: GDScript
|
||||
|
||||
var _field: CodeEdit
|
||||
var _session: GDScriptInteractiveSession
|
||||
var _session: RefCounted
|
||||
var _history: PackedStringArray
|
||||
var _history_index: int
|
||||
|
||||
|
@ -38,7 +40,7 @@ func _submit() -> void:
|
|||
if not code.is_empty():
|
||||
_history.append(code)
|
||||
print("> %s" % code)
|
||||
var result := _session.eval(code)
|
||||
var result := _session.eval(code) as Variant
|
||||
print("=> %s" % var_to_str(result))
|
||||
_history_index = _history.size()
|
||||
_history_recall()
|
||||
|
@ -103,7 +105,9 @@ func _enter_tree() -> void:
|
|||
_field.scroll_fit_content_height = true
|
||||
_field.placeholder_text = "Evaluate GDScript"
|
||||
# Create other vars.
|
||||
_session = GDScriptInteractiveSession.new()
|
||||
_session = load(
|
||||
"res://addons/gdscript_input_field/gdscript_interactive_session.gd"
|
||||
).new()
|
||||
_session.inject("_editor_interface", get_editor_interface())
|
||||
_history = PackedStringArray([])
|
||||
_history_index = 0
|
||||
|
|
Loading…
Reference in New Issue