Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot call Callable in a draw method (because no way to set_object on Callable?) #75376

Closed
OmarShehata opened this issue Mar 27, 2023 · 6 comments · Fixed by #89298
Closed

Comments

@OmarShehata
Copy link
Contributor

Godot version

v4.0.1.stable.official [cacf499]

System information

Windows 11

Issue description

If you try to pass a lambda/callable to be used inside a draw function, you get the error below. As far as I can tell, this is because the object that the callable is attached to is NOT the class that is calling the _draw function, so Godot doesn't recognize that it's actually being called inside _draw ?

 Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal.
  <C++ Error>    Condition "!drawing" is true.
  <C++ Source>   scene/main/canvas_item.cpp:599 @ draw_rect()

Steps to reproduce

class CustomDrawNode extends Node2D:
	var draw_func:Callable
	
	func _init(custom_draw_func:Callable):
		draw_func = custom_draw_func

	func _draw():
		draw_func.call()

# Then instantiate this as follows
var custom_draw_node = CustomDrawNode.new(func(): draw_rect(Rect2(0.0, 0.0, 100.0, 100.0), Color.GREEN)
	)
add_child(custom_draw_node)

I think to fix this I would want to do something like

draw_func.set_object(self)

so that the passed in custom_draw_func can be called with the CustomDrawNode as its self?

Minimal reproduction project

N/A

Can be reproduced completely with the snippet above.

@kleonc
Copy link
Member

kleonc commented Mar 27, 2023

If you try to pass a lambda/callable to be used inside a draw function, you get the error below. As far as I can tell, this is because the object that the callable is attached to is NOT the class that is calling the _draw function, so Godot doesn't recognize that it's actually being called inside _draw ?

You're kinda right. This is because the object that the callable is attached to is NOT the object (a CanvasItem) that is currently within its drawing routine (NOTIFICATION_DRAW / draw signal / _draw method). So the error itself is correct, the error message not really (as it's not just about being called inside _draw, but rather about being called inside _draw of the specific CanavasItem, the one currently being drawn (the current_item_drawn)).

The error messages like:

ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal.");

can be improved for cases when current_item_drawn != nullptr && current_item_drawn != this as for them the execution is actually "inside NOTIFICATION_DRAW, _draw() function or 'draw' signal", just not of the proper CanvasItem.


Steps to reproduce

class CustomDrawNode extends Node2D:
	var draw_func:Callable
	
	func _init(custom_draw_func:Callable):
		draw_func = custom_draw_func

	func _draw():
		draw_func.call()

# Then instantiate this as follows
var custom_draw_node = CustomDrawNode.new(func(): draw_rect(Rect2(0.0, 0.0, 100.0, 100.0), Color.GREEN)
	)
add_child(custom_draw_node)

Currently you'd need to pass the drawing CanvasItem as the Callable's argument. Like so:

class CustomDrawNode extends Node2D:
	var draw_func: Callable
	
	func _init(custom_draw_func: Callable):
		draw_func = custom_draw_func

	func _draw():
		draw_func.call(self)

# Then instantiate this as follows
var custom_draw_node = CustomDrawNode.new(
	func(ci: CanvasItem):
		ci.draw_rect(Rect2(0.0, 0.0, 100.0, 100.0), Color.GREEN)
)
add_child(custom_draw_node)

I'd say this is expected.


I think to fix this I would want to do something like

draw_func.set_object(self)

so that the passed in custom_draw_func can be called with the CustomDrawNode as its self?

To me this sounds more like a material for a proposal.

@kleonc kleonc added the bug label Mar 27, 2023
@kleonc
Copy link
Member

kleonc commented Mar 27, 2023

Marking as bug because of the incorrect / confusing error message.

@OmarShehata
Copy link
Contributor Author

draw_func.call(self)

Ah yeah, that works perfectly well for my use case! I was kind of stuck on this pattern because in JavaScript, given a function you can say "call it with this object as the 'self' (or this) param" (link) but didn't think to just explicitly pass the obj I want to apply it to.

Thank you very much for the explanation @kleonc !

@AThousandShips
Copy link
Member

AThousandShips commented Mar 27, 2023

To replace the owner of a callable you can also do (sorry missed this was a lambda for which this doesn't work) (old and new can be the same, just making clere here):

callable_new = Callable(target, callable_old.get_method()).bindv(callable_old.get_bound_arguments())

@kleonc
Copy link
Member

kleonc commented Mar 27, 2023

To replace the owner of a callable you can also do:

callable_new = Callable(target, callable_old.get_method()).bindv(callable_old.get_bound_arguments())

This won't always work. It should work e.g. when callable_old is not a lambda and the type (including scripts) of target and callable_old.get_object() match (so the given method could be found by name both in target and in callable_old.get_object()). But won't work in general case, e.g. for the use case mentioned by the OP.

@AThousandShips
Copy link
Member

True! Forgot about lambdas

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants