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:
blujai831 2023-10-15 08:44:22 -07:00
parent 42d36ee94b
commit 78fd86fbbc
Signed by: blujai831
GPG Key ID: DDC31A0363AA5E66
5 changed files with 78 additions and 138 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -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"

View File

@ -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