How to check if node has property without instancing? - godot

I'm trying to check if a certain node type has a property
without actually needing to make an instance of it
like this:
print("z_index" in Position2D);

Classes in ClassDB
If we are talking about a build-in class (not a custom class that you created, but one that is part of Godot), you can use ClassDB to get the property:
var properties := ClassDB.class_get_property_list("Position2D")
Classes from Godot Scripts
If the class is not in ClassDB (which is the case custom classes), but you have the script, you can use the script to get the property list:
var properties := preload("res://custom_class.gd").get_script_property_list()
If you don't have the script, perhaps you can find it. This code uses the hidden project setting "_global_script_classes" to find the path of the script for a class given the name_of_class you are looking for, and loads it:
if ProjectSettings.has_setting("_global_script_classes"):
for x in ProjectSettings.get_setting("_global_script_classes"):
if x.class == name_of_class:
return load(x.path)
Addendum: This is no longer available in Godot 4.
Other classes
However, the above approach will not work for every type of script. In those cases, I'm afraid the best way is to instance it. You can still get the properties from the instance and cache them (perhaps put them in a dictionary) so that you are not creating a new instance every time you need to query:
var properties := (CustomClass.new()).get_property_list()
Query the properties
Regardless of how you got the property list, you can query them the same way. For example this code looks for a property with name "z_index" and gets its type:
var found := false
var type := TYPE_NIL
for property in properties:
if property.name == "z_index":
found = true
type = property.type
break
prints(found, type)
The type is a Variant.Type constant.

Theraot's answer is correct, since it provides a way to check attributes without creating an instance of a node/gdscript.
You can also check the properties of an existing instance of a node/scene by doing this:
if "attribute_name" in thing:
pass # do stuff here
Practical example; During a signal triggered by two Area2Ds colliding, check if one node's attribute item_type is set:
func _on_area_2d_area_entered(area):
if "item_type" in area:
print(area["item_type"])

Related

Is there way to create TRUE custom type in Godot?

I'm trying to create a custom type for my Player Controller.
First I went with "script classes":
extends Node
class_name PlayerController
export var max_speed = 32
export var id = 1
export(NodePath) var node_path
onready var node = get_node(node_path)
...
But then I realized, and was not quite satisfied with the way that properties are displayed:
I'd expected it to be in the "PlayerController" dedicated section as it with node, so it will be possible to inherit and see the nice stack of properties there.
I thought that it is due to the "script classes" being a simplified comparing to regular extensions. So I created one.
tool
extends EditorPlugin
func _enter_tree():
add_custom_type("PlayerController", "Node", preload("PlayerController.gd"),preload("PlayerController.svg"))
func _exit_tree():
pass
The result was literally the same.
What I figured out is that "custom type" created in this way is not a "custom type" but just a Parent type with script attached to it. It can be confirmed in the docs:
void add_custom_type(type: String, base: String, script: Script, icon: Texture)
Adds a custom type, which will appear in the list of nodes or resources. An icon can be optionally passed.
When given node or resource is selected, the base type will be instanced (ie, "Spatial", "Control", "Resource"), then the script will be loaded and set to this object.
You can use the virtual method handles() to check if your custom object is being edited by checking the script or using the is keyword.
During run-time, this will be a simple object with a script so this function does not need to be called then.
My question is:
It is possible to create a true custom type?
Adding a category in the inspector panel
First of all, if what you want is a dedicated section in the inspector panel, you can do that with a tool (see Running code in the editor) script and _get_property_list.
This is an example based on another answer:
tool
extends Node
export var something_else:String
var custom_position:Vector2
var custom_rotation_degrees:float
var custom_scale:Vector2
func _get_property_list():
return [
{
name = "Custom",
type = TYPE_NIL,
hint_string = "custom_",
usage = PROPERTY_USAGE_CATEGORY
},
{
name = "custom_position",
type = TYPE_VECTOR2
},
{
name = "custom_rotation_degrees",
type = TYPE_REAL
},
{
name = "custom_scale",
type = TYPE_VECTOR2
}
]
There I'm creating a category called "Custom". Notice that type is TYPE_NIL and usage is PROPERTY_USAGE_CATEGORY. Which is how the documentation does it. See Adding script categories.
Using PROPERTY_USAGE_CATEGORY will produce a named header, similar to the one that says "Node" on the picture on the question. Use PROPERTY_USAGE_GROUP to make a collapsible group.
The value of hint_string will be used as a prefix. Any property with that prefix will be in that category. Such as the ones you see in the code, each with their respective types. These are all declared with var in my code. However, not with export because then they would appear twice.
And it looks as follows:
Adding a custom type
When we create a a class in a script, it is a type. A GDScript type. For evidence that they are GDScript types. Notice that when using class_name you can declare variables of that type, check if a value is of the type with the is operator. And that you can use extends with them.
There are also core types. Which are also GDScript types. So you can also use declare variable of the type, use extend with them and check for them with is.
However, the classes we create in a script are not core types. We cannot really create a core type from GDScript or any other script language. All we can do is add a script to an existing core type.
If you want to create actual core types, you need to use C++ and create a Godot Module.
You may also be interested in GDExtension in the upcoming Godot 4.0.

Is the `def` keyword optional? If so, why use it?

I am aware that a variable can be dynamically typed with the def keyword in Groovy. But I have also noticed that in some circumstances it can be left out, such as when defining method parameters, eg func(p1, p2) instead of func(def p1, def p2). The latter form is discouraged.
I have noticed that this is extendable to all code - anytime you want to define a variable and set its value, eg var = 2 the def keyword can be safely left out. It only appears to be required if not instantiating the variable on creation, ie. def var1 so that it can be instantiated as a NullObject.
Is this the only time def is useful? Can it be safely left out in all other declarations, for example, of classes and methods?
Short answer: you can't. There are some use cases where skipping the type declaration (or def keyword) works, but it is not a general rule. For instance, Groovy scripts allow you to use variables without specific type declaration, e.g.
x = 10
However, it works because groovy.lang.Script class implements getProperty and setProperty methods that get triggered when you access a missing property. In this case, such a variable is promoted to be a global binding, not a local variable. If you try to do the same on any other class that does not implement those methods, you will end up getting groovy.lang.MissingPropertyException.
Skipping types in a method declaration is supported, both in dynamically compiled and statically compiled Groovy. But is it useful? It depends. In most cases, it's much better to declare the type for a better readability and documentation purpose. I would not recommend doing it in the public API - the user of your API will see Object type, while you may expect some specific type. It shows that this may work if your intention is to receive any object, no matter what is its specific type. (E.g. a method like dump(obj) could work like that.)
And last but not least, there is a way to skip type declaration in any context. You can use a final keyword for that.
class Foo {
final id = 1
void bar(final name) {
final greet = "Hello, "
println greet + name + "!"
}
}
This way you can get a code that compiles with dynamic compilation, as well as with static compilation enabled. Of course, using final keyword prevents you from re-assigning the variable, but for the compiler, this is enough information to infer the proper type.
For more information, you can check a similar question that was asked on SO some time ago: Groovy: "def" keyword vs concrete type
in Groovy it plays an important role in Global and Local variable
if the variable name is same with and without def
def is considered local and without def its global
I have explained here in detail https://stackoverflow.com/a/45994227/2986279
So if someone use with and without it will make a difference and can change things.

Groovy - Add property or method dynamically to metaClass of this

I want to dynamically add a field and methods to the metaClass of my current object. I tried
this.metaClass.testProp = "test"
to add a field called testProp. However, I get
groovy.lang.MissingPropertyException: No such property: testProp for class: groovy.lang.MetaClassImpl
When I do the same on class level, e.g. adding testProp to the class and not directly to the object
Process.metaClass.testProp = "test"
it works, but my object does NOT inherit the field. Any ideas or pointers in the right direction would be greatly appreciated.
Short answer:
Process.metaClass.testProp = "test"
this.metaClass = null
assert this.testProp == "test"
Long answer:
I assume, there are 3 things that make you a problem here. The first one is that there is a global registry for meta classes. The second is that Groovy allowed per instance meta classes (and this is the default for Groovy classes). And the third one is that the default meta class does not allow runtime meta programming.
So what happens if you do runtime meta programming is that the default needs to replaced by ExpandoMetaClass (EMC), to allow for that. But since there is a per instance meta class logic, this replacement may not affect all instances. The meta class of the instance is taken from the global registry the first time it is used. Process.metaClass.testProp = "test" changes the global saved one. Any instance that already has a meta class, will not know of the change and thus not know the property. this.metaClass.testProp = "test" will change only the meta class of the current instance, other instances may not know of it. You can use this.metaclass = null to reset it. And it will again request the meta class from the global registry.If you did the per instance change, your change is away. If you did the global change, your change is visible now.
All this counts if you work with the default meta class in Groovy (MetaClassImpl). If you do a change like a in your code, the new meta class will be ExpandoMetaClass (EMC). This meta class allows for changes, so further changes will not cause a replacement of the meta class. To ensure all instance take an ExpandoMetaClass from the beginning you normally have some kind of setup code like this: ExpandoMetaClass.enableGlobally()
So to solve your question I could simply do this
Process.metaClass.testProp = "test"
this.metaClass = null
assert this.testProp == "test"
If you used ExpandoMetaClass.enableGlobally() in some setup code before, you can leave the reset code for the meta class out (or if the global meta class is the same as the one for "this" and if it is EMC). Grails for example uses EMC as default

How do I add a globally available MetaMethod on Object in Groovy?

(this is a generalized example)
I'd like to create a utility method that can be called on any object, it'll have a signature like:
class StringMetaData {
Object value
String meta
}
Object.metaClass.withStringMetaData = { meta ->
new StringMetaData(delegate, meta)
}
With the idea that then anywhere in my program I could do something like:
def foo = 1.withStringMetaData("bar")
And now I can grab foo.value for the value or foo.meta for the attached String.
Within a local context, I'm able to define this meta method on Object, but I'd like to make it available globally within my application, what's the right way to make this metamethod available everywhere?
Perhaps a groovy extension module could help you. I never tried it myself, but the documentation states, that you can add custom methods to JDK classes.

How to auto-generate early bound properties for Entity specific (ie Local) Option Set text values?

After spending a year working with the Microsoft.Xrm.Sdk namespace, I just discovered yesterday the Entity.FormattedValues property contains the text value for Entity specific (ie Local) Option Set texts.
The reason I didn't discover it before, is there is no early bound method of getting the value. i.e. entity.new_myOptionSet is of type OptionSetValue which only contains the int value. You have to call entity.FormattedValues["new_myoptionset"] to get the string text value of the OptionSetValue.
Therefore, I'd like to get the crmsrvcutil to auto-generate a text property for local option sets. i.e. Along with Entity.new_myOptionSet being generated as it currently does, Entity.new_myOptionSetText would be generated as well.
I've looked into the Microsoft.Crm.Services.Utility.ICodeGenerationService, but that looks like it is mostly for specifying what CodeGenerationType something should be...
Is there a way supported way using CrmServiceUtil to add these properties, or am I better off writing a custom app that I can run that can generate these properties as a partial class to the auto-generated ones?
Edit - Example of the code that I would like to be generated
Currently, whenever I need to access the text value of a OptionSetValue, I use this code:
var textValue = OptionSetCache.GetText(service, entity, e => e.New_MyOptionSet);
The option set cache will use the entity.LogicalName, and the property expression to determine the name of the option set that I'm asking for. It will then query the SDK using the RetrieveAttriubteRequest, to get a list of the option set int and text values, which it then caches so it doesn't have to hit CRM again. It then looks up the int value of the New_MyOptionSet of the entity and cross references it with the cached list, to get the text value of the OptionSet.
Instead of doing all of that, I can just do this (assuming that the entity has been retrieved from the server, and not just populated client side):
var textValue = entity.FormattedValues["new_myoptionset"];
but the "new_myoptionset" is no longer early bound. I would like the early bound entity classes that gets generated to also generate an extra "Text" property for OptionSetValue properties that calls the above line, so my entity would have this added to it:
public string New_MyOptionSetText {
return this.GetFormattedAttributeValue("new_myoptionset"); // this is a protected method on the Entity class itself...
}
Could you utilize the CrmServiceUtil extension that will generate enums for your OptionSets and then add your new_myOptionSetText property to a partial class that compares the int value to the enums and returns the enum string
Again, I think specifically for this case, getting CrmSvcUtil.exe to generate the code you want is a great idea, but more generally, you can access the property name via reflection using an approach similar to the accepted answer # workarounds for nameof() operator in C#: typesafe databinding.
var textValue = entity.FormattedValues["new_myoptionset"];
// becomes
var textValue = entity.FormattedValues
[
// renamed the class from Nameof to NameOf
NameOf(Xrm.MyEntity).Property(x => x.new_MyOptionSet).ToLower()
];
The latest version of the CRM Early Bound Generator includes a Fields struct that that contains the field names. This allows accessing the FormattedValues to be as simple as this:
var textValue = entity.FormattedValues[MyEntity.Fields.new_MyOptionSet];
You could create a new property via an interface for the CrmSvcUtil, but that's a lot of work for a fairly simple call, and I don't think it justifies creating additional properties.

Resources