godot-python icon indicating copy to clipboard operation
godot-python copied to clipboard

Is it possible for one python Node to communicate directly with another?

Open whogben opened this issue 5 years ago • 6 comments

Example Case: FrameStream.py is a node subclass that receives images from the network. PyFrame.py is a node subclass that does some work on an image.

I am trying to set the .image attribute on my PyFrame node directly from FrameStream.py, but I keep running into trouble leading me to wonder if there's any way to do it.

When I try the relatively simple "pyframe.image = image" inside of FrameStream.py, I get "AttributeError: 'Node' object has no attribute 'set_image'"

When I print(str(pyframe)) to see what class it thinks it is, I get "<godot.hazmat.base.Node object at 0x137ef0ac8>"

I need to pass the image from the one Python based node to the other without it going through godot translation, because that adds too much latency - and since i need it in Python at both ends, it would need double-translation. Is there a way to get direct in-python communication between two python based nodes?

Edit for anyone reading now: I found a workaround - create a singleton of pure python class that can be used as an image cache, and then I send the key of the image in the cache to the other python node, which retrieves it from the cache.

whogben avatar Jul 02 '20 22:07 whogben

This is a problem I've run into a lot of times too. You should be able to do it by making the image property exported and a native godot type, but that incurs in the expensive coercion: python -> godot -> python (convert a python image to a godot image and then back again in the other side).

@touilleMan is there an easy way to solve this? I was thinking of keeping a table of intances somewhere, and when someone calls .get_node , get_child, get_children etc, we lookup this table and return the python instance instead of a wrapper. what do you think?

matheus2740 avatar Jul 03 '20 12:07 matheus2740

Returning a generic Node for all the get_node/get_child/get_children methods is indeed a pretty poor thing to do :'(

I thought we already had a casting solution in place for this: https://github.com/touilleMan/godot-python/blob/485ab90532da9cf751f25c3870eba48e4ae8060e/tools/bindings_templates/method.tmpl.pyx#L44

https://github.com/touilleMan/godot-python/blob/485ab90532da9cf751f25c3870eba48e4ae8060e/tools/bindings_templates/class.tmpl.pyx#L65-L75

I guess the trouble is this casting system allow to return Godot builtins classes (e.g. KinematicBody) but not custom ones defined in Python.

However I guess we could improve Object.cast_from_ptr by also looking for the script type

touilleMan avatar Jul 06 '20 21:07 touilleMan

Hi, I'm trying to load an AudioStreamPlayer node in a script to use it to play sounds.

Is this possible?

I used asp = export(NodePath) to get an AuioStreamplayer node but how do I derive the node from the NodePath?

This seems to be related to above discussion, if I'm not mistaken?

GamesOpenSource avatar Dec 19 '20 15:12 GamesOpenSource

get_node(asp) should work as far as I know. As far as I can tell this isn't related to the original question, which was about communications between python-based-nodes, and this would be accessing a godot-based node (AudioStreamPlayer) from a python node? I would expect this works - I definitely access other godot nodes from python currently.

whogben avatar Dec 25 '20 23:12 whogben

@whogben I'm slightly confused as to how your workaround works. With singleton, do you mean what is described in the How can I autoload a python script without attaching it to a Node? bit of the readme? I tried that but it just tells me NameError: name 'autoloadpy' is not defined. Is there something I'm missing?

CombustibleLemonade avatar Aug 21 '22 22:08 CombustibleLemonade

It's not the cleanest solution but my workaround for this is shown below. I hope in the Godot 4 version we can just directly call between Python objects. Thank you @touilleMan for all your work on this, it's a great library!

# bridge.gd (autoloaded as 'Bridge')
extends Node

func call_py(callee_name: String, method_name: String, args: Array = []):
  return get_node('/root/%s' % callee_name).callv(method_name, args)

# foo.py
from godot import Array, Dictionary, Node, exposed

# I actually have this in a utils.py file because I use it all over the place
def to_gd(var):
  if isinstance(var, (list, tuple)):
    return Array([to_gd(item) for item in var])
  if isinstance(var, dict):
    return Dictionary({to_gd(key): to_gd(value) for key, value in var.items()})

@exposed
class Foo(Node):
  def _ready():
    bridge = self.get_node('/root/Bridge')
    x = bridge.call_py('Bar', 'do_something', to_gd([1, 2]))  # x = 3, see below

# ---------
# bar.py
from godot import Array, Dictionary, GDString, Node, exposed

# I actually have this in a utils.py file, like `to_gd`, because I use it all over the place
def from_gd(var):
    if isinstance(var, (tuple, list)):
        py_var = [from_gd(item) for item in var]
        if isinstance(var, tuple):
            py_var = tuple(py_var)
        return py_var
    if isinstance(var, Array):
        py_var = [from_gd(item) for item in var]
        return py_var
    if isinstance(var, Dictionary):
        py_keys = [from_gd(key) for key in var.keys()]
        py_values = [from_gd(value) for value in var.values()]
        py_var = dict(zip(py_keys, py_values))
        return py_var
    if isinstance(var, GDString):
        py_var = str(var)
        return py_var
    return var

@exposed
class Bar(Node):
    def do_something(self, x, y):
        return from_gd(x) + from_gd(y)  # obviously in the case of ints the call to `from_gd` isn't needed but this is just an example

jdrempel avatar Mar 03 '23 21:03 jdrempel