Are accessors of class properties useful? - encapsulation

Consider the following accessor methods of the property global_position of the class Node2D:
Vector2 global_position
Setter set_global_position(value)
Getter get_global_position()
But the property is not encapsulated as shown by this example:
tool
extends EditorScript
func _run() -> void:
var n = Node2D.new()
n.global_position = Vector2(100, 100)
print(n.global_position)
which produces:
* scene/2d/canvas_item.cpp:467 - Condition "!is_inside_tree()" is true. Returned: get_transform()
(100, 100)
Are those accessors useless?

They are NOT useless. They are useful… If the node is in the scene tree.
You can add the node to the scene tree with add_child or add_child_below_node.
I'm not sure what you mean by "not encapsulated". Just in case, I'll point out that you are NOT bypassing them.
When you use the property, you are using the getter and setter methdods. The property is a convenience for language binding. Thus, we could also say that you don't need the property, only the getter and setter.
You can see in the source for Node2D, that there is a _bind_methods function that sets all the properties and methods that are exposed to use. This is how global_position looks like:
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "", 0), "set_global_position", "get_global_position");
That message you are getting is because you are using global_position on a node that is not on the scene tree. And, yes in that case, it is not useful. We can figure that out with a simple script:
extends Node2D
func _ready() -> void:
global_position = Vector2(200, 300)
var n = Node2D.new()
n.global_position = Vector2(100, 100)
print(n.global_position)
add_child(n)
print(n.global_position)
This outputs:
(100, 100)
(300, 400)
Thus, as you can see, it will have a different global position from what was set. And the difference depends on the position of the parent. Setting position would have the same effect. Thus setting global_position not useful here.
If you want to go deeper on what global_position does, we can have a look at the source of the getter and setter (part of the linked source for Node2D):
Point2 Node2D::get_global_position() const {
return get_global_transform().get_origin();
}
void Node2D::set_global_position(const Point2 &p_pos) {
Transform2D inv;
CanvasItem *pi = get_parent_item();
if (pi) {
inv = pi->get_global_transform().affine_inverse();
set_position(inv.xform(p_pos));
} else {
set_position(p_pos);
}
}
This is set_position, by the way (notice it writes pos):
void Node2D::set_position(const Point2 &p_pos) {
if (_xform_dirty)
((Node2D *)this)->_update_xform_values();
pos = p_pos;
_update_transform();
_change_notify("position");
}
And _update_transform (with conspicuous !is_inside_tree() check):
void Node2D::_update_transform() {
_mat.set_rotation_and_scale(angle, _scale);
_mat.elements[2] = pos;
VisualServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), _mat);
if (!is_inside_tree())
return;
_notify_transform();
}
Notice that _update_transform updates _mat based on pos.
What about get_global_transform? Not in that file. We find it in the source for CanvasItem:
Transform2D CanvasItem::get_global_transform() const {
#ifdef DEBUG_ENABLED
ERR_FAIL_COND_V(!is_inside_tree(), get_transform());
#endif
if (global_invalid) {
const CanvasItem *pi = get_parent_item();
if (pi) {
global_transform = pi->get_global_transform() * get_transform();
} else {
global_transform = get_transform();
}
global_invalid = false;
}
return global_transform;
}
And there is the failed assert you saw: !is_inside_tree().
Oh, about that global_invalid. If you search for it on the source, you will find it is set to true when the node exits the scene tree or the transform is modified (that is in _notify_transform, which I'm not including here, but you can see it is called by _update_transform when it is in the scene tree).
What can we make out of all this?
The property global_position is simply syntactic sugar for the methods get_global_position and set_global_position.
The methods get_global_position and set_global_position work on the global transform, which is inherited from CanvasItem.
To figure out the global position, we need to work out the transforms on the parent nodes. Which means, we could also figure it out by going over the same process, and thus these methods technically not necessary.
It is done lazily. The global position gets invalidated, and it is recomputed on demand.
If the node is not in the scene tree… What parent nodes? In this case using global_position would be doing the same if we were using position. Thus, we could say global_position is not useful when the node is not in the scene tree.
There is an assert that tells you when you are using it and it is not in the scene tree. And that assert gave you the message you posted.

Related

How to handle NSOutlineViewDataSource with loops

I have an NSOutlineView populated from an NSOutlineViewDataSource which is a thin wrapper over a supplied object model, containing a mixture of arrays, dictionaries and primitive types.
However the object model has loops in its hierarchy. I thought that this shouldn't be a problem, as a user would not expand that node (or get bored after a while) but the two different paths refer to the same object instance, what happens to one happens to the other, if the parent is expanded (as it would be) then the child item is also expanded resulting in an infinite loop.
I've tried putting conditional logic into my DataSource, to not return an object if the document dictionary key is "Parent". But all I see in isItemExpandable is a reference to the object, and I have no idea if the key is "Parent" or not.
I've tried caching the objects in an NSDictionary, with their keys, to see if I've encountered them before, this allowed me to determine the key name and return NO for isItemExpandable this partially worked but as they are same object, the parent's object key was overwritten with "Parent" changing the name shown in the NSOutlineView and preventing the "Parent" key from being expanded or collapsed.
The datasource is populated via callbacks, I haven't got much context to determine if the object is the parent node, or the child, let alone if it is referenced multiple times.
I've seen a similar questions refer to NSPathIndex but that appears to be for array indexes and not dictionary keys, NSTreeNode has a similar problem and there doesn't appear to be an NSKeyPath class.
Does anyone know how to handle this case?
Each item in the outline view must be unique. Wrap each node in a NSTreeNode or similar custom class. Repeated nodes are wrapped again. For example:
MyObject is a NSObject subclass with property children but representedObject can be anything.
#property NSTreeNode *rootNode;
// setup
self.rootNode = [[NSTreeNode alloc] initWithRepresentedObject:rootObject];
[self.outlineView reloadData];
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(NSTreeNode *)item {
if (!item)
item = self.rootNode;
MyObject *object = item.representedObject;
return object.children.count;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(NSTreeNode *)item {
if (!item)
item = self.rootNode;
MyObject *object = item.representedObject;
/*
// show repeated nodes as leaves
NSTreeNode *parentItem = item.parentNode;
while (parentItem) {
if (parentItem.representedObject == object)
return NO;
parentItem = parentItem.parentNode;
}
*/
return object.children.count > 0;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(NSTreeNode *)item {
if (!item)
item = self.rootNode;
if (index >= item.childNodes.count) {
// create child nodes
NSMutableArray *childrenArray = item.mutableChildNodes;
[childrenArray removeAllObjects];
MyObject *object = item.representedObject;
for (MyObject *childObject in object.children)
[childrenArray addObject:[[NSTreeNode alloc] initWithRepresentedObject:childObject]];
}
return item.childNodes[index];
}

Scenekit - SCNLookupConstraint causes ConvertPosition of childNode not to update its position

I have a main node, with a sequence of childNodes. The childNodes are firePoints, so as the target comes into view, the main node rotates to target and the firepoint is an offset where I need to shoot from. It works fine if I target via some vector classes I built, but it is smoother if I use SCNConstraint on the main node. The main node (and firepoints) rotate to target, but the fire points vector values do not ever change when convertPosition is called. I can see that the fireNodes are rotating along with the base node properly. Thanks
func shoot()
{
isShooting = true
// Convert position so that projectile fires from FirePoint
let fireNode = gNodes.getNode(vName: attr.name + "FirePoint" + "\(attr.firePointsSequence)", vRequired: true, vError: "FP0-Sproj")
let fireNodeStart = gNodes.gameNodes.convertPosition(fireNode.presentation.position, from: attr.node)
print("FireNodePosition: \(fireNodeStart)
}
func setTarget()
{
attr.node.constraints = []
let vConstraint = SCNLookAtConstraint(target: targetNode)
vConstraint.isGimbalLockEnabled = true
attr.node.constraints = [vConstraint]
}
I had this problem. What solved it for me was using SCN's presentation scn. It has up to date positions.

How to get the class of a VALUE in Ruby C API

I created some classes with Ruby's C API. I want to create a function whose behavior will change depending on the class of the Ruby object.
I tried to use is_a? from Ruby, however, I don't think it's the good way to do this. I checked "Creating Extension Libraries for Ruby" without success. The only direct way to check classes is with the default types.
I have my class "Klass" already created:
VALUE rb_cKlass = rb_define_class("Klass", rb_cObject);
And how I wanted to check if the class is the good one:
VALUE my_function(VALUE self, VALUE my_argument) {
if(rb_check_class(my_argument), rb_cKlass)) {
// do something if my_argument is an instance of Klass
} else {
return Qnil;
}
}
Is there a way to do this?
I came across this recently, and used the RBASIC_CLASS macro, but was getting segfaults in certain scenarios for some unexplained reason.
After scanning through ruby.h, I found the CLASS_OF macro, which returns the class as VALUE of a given object.
VALUE obj = INT2NUM(10);
VALUE klass = CLASS_OF(obj); // rb_cInteger
Using Ruby 2.5
Every ruby object is internally represented by RObject struct (I will copy the source here for the sake of future readers):
struct RObject {
struct RBasic basic;
union {
struct {
uint32_t numiv;
VALUE *ivptr;
void *iv_index_tbl; /* shortcut for RCLASS_IV_INDEX_TBL(rb_obj_class(obj)) */
} heap;
VALUE ary[ROBJECT_EMBED_LEN_MAX];
} as;
};
The very first member, RBasic, defines the class:
struct RBasic {
VALUE flags;
const VALUE klass;
}
To get an access to RBasic metadata of anything, one might use RBASIC macro:
RBASIC(my_argument)
To get the class directly, one might use RBASIC_CLASS macro:
RBASIC_CLASS(my_argument)
If you want to stay close to the is_a? Ruby fashion (i.e. check if any of the ancestors is the expected class), you could directly use the C implementation of is_a?, rb_obj_is_kind_of:
rb_obj_is_kind_of(my_argument, rb_cKlass) // Qtrue OR Qfalse
And since Qfalse == 0, you can just use that method as a condition:
VALUE my_function(VALUE self, VALUE my_argument) {
if(rb_obj_is_kind_of(my_argument, rb_cKlass)) {
// do something if my_argument is an instance of Klass
} else {
return Qnil;
}
}
To find this method, just check Object#is_a? documentation and click to toggle source, you'll see the C implementation if it is a C function (hence this will work for most of the standard lib).

Duktape/C array element accessors

I am trying to implement something like a HTMLCollection which is an array that can lose/gain elements without JS action.
duk_push_object(ctx);
duk_push_string(ctx, "length");
duk_push_c_function(ctx, my_length_getter, 1);
duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_GETTER);
duk_push_c_function(ctx, my_item_getter, 1);
duk_put_prop_string(ctx, -2, "item");
Doing it like above I get an object on which I can read the my_array.length property and get an item by calling method my_array.item(index). But I don't get an item by using my_array[index].
If I replace the first line above by
duk_push_array(ctx);
I get an error that the length property is not configurable.
Is it possible to achieve what I want? An array with 'lazy' element binding? I have the impression that NetSurf somehow manages to do this but haven't quite worked out how ...
Ecmascript provides two main standard mechanisms for property virtualization: getters/setters (accessors) and the Proxy object. Getters/setters are limited to properties you explicitly set up beforehand so they don't always work for fully virtualizing an object, but a Proxy object can capture among other things all property reads and writes.
You should be able to implement your use case using a Proxy. Duktape implements a subset of the Proxy traps (documented in http://duktape.org/guide.html#es6-proxy). As a minimal example of capturing all property reads and writes (but forwarding them to the target):
var target = { foo: 'hello' };
var proxy = new Proxy(target, {
get: function (targ, key) {
print('get', key);
// may also return a virtualized value
return targ[key];
},
set: function (targ, key, val) {
print('set', key, val);
// may also capture (virtualize) write, or reject write
targ[key] = val;
return true; // indicate write was allowed
}
});
print(proxy.foo);
proxy.bar = 123;
Running with "duk" this prints:
get foo
hello
set bar 123

Instantiation attempted on a non-constructor

I'm trying to make a bomb catching game (I'm actually using the code from the AS3 Classroom in a Book on arrays). As soon as I changed the word fruit to bomb in the code I got error 1007. With the exception of changing basket_mc to eod_mc and fruit to bomb (I used command-F and replaced with case sensitive on) I haven't changed much. It worked with eod_mc, but doesn't with bomb.
var bombArray:Array = new Array(bomb);
var bombsOnstage:Array = new Array();
var bombsCollected:int = 0;
var bombsLost:int = 0;
for (var i:int = 0; i<20; i++) {
var pickBomb = bombArray[int(Math.random() * bombArray.length)];
var bomb:MovieClip = new pickBomb();
addChild(bomb);
bomb.x = Math.random() * stage.stageWidth-bomb.width;// bomb.width is subtracted from the random x position to elimate the slight possibility that a clip will be placed offstage on the right.
bomb.y = Math.random() * -500;
bomb.speed = Math.random() * 15 + 5;
bombsOnstage.push(bomb);
}
eod_mc.addEventListener(MouseEvent.MOUSE_DOWN, dragEod);
stage.addEventListener(MouseEvent.MOUSE_UP, dragStop);
function dragEod(e:Event):void {
eod_mc.startDrag();
}
function dragStop(e:Event):void {
eod_mc.stopDrag();
}
stage.addEventListener(Event.ENTER_FRAME, catchBomb);
function catchBomb(e:Event):void {
for (var i:int = bombsOnstage.length-1; i > -1; i--) {
var currentBomb:MovieClip = bombsOnstage[i];
currentBomb.y += currentBomb.speed;
if (currentBomb.y > stage.stageHeight - currentBomb.height) {
currentBomb.y = 0 - currentBomb.height;
bombsLost++;
field2_txt.text = "Total Bombs Detonated: " + bombsLost;
}
if (currentBomb.hitTestObject(eod_mc)) {
bombsCollected++;
removeChild(currentBomb);
bombsOnstage.splice(i,1);
field1_txt.text = "Total Bombs Caught: " + bombsCollected;
if (bombsCollected >= 20) {
eod_mc.gotoAndStop(20);
} else if (bombsCollected > 15) {
eod_mc.gotoAndStop(15);
} else if (bombsCollected>10) {
eod_mc.gotoAndStop(10);
} else if (bombsCollected>5) {
eod_mc.gotoAndStop(5);
}
}
}
if (bombsOnstage.length <= 0) {
field1_txt.text = "You Win! You have defused the bombs.";
field2_txt.text = "";
stage.removeEventListener(Event.ENTER_FRAME, catchBomb);
}
if (bombsLost >= 20) {
field1_txt.text = "Sorry you lose. You have lost your foot!";
field2_txt.text = "";
stage.removeEventListener(Event.ENTER_FRAME, catchBomb);
for (var j:int = bombsOnstage.length-1; j > -1; j--) {
currentBomb = bombsOnstage[j];
removeChild(currentBomb);
bombsOnstage.splice(j,1);
}
}
}
Maybe to avoid making your programming life more twisted than it is now...
There are objects and classes in programming, a class is a description of a set of objects, say "table" (or, as it's better to differ in names for classes and variables, "Table", first letter capitalized) is a name of a class. An instance or an object is a structure that belongs to one or more classes, with Object being the topmost, as everything in programming is either an object or a "simple variable", that is, a number, a true/false, a string of characters (these are object types in AS3 too, though, Number, Boolean, String, but these generally not need to be instantiated via new) or probably some other simple type I don't remember right now.
Classes have properties and methods. A property is something that can be requested off any object of the class, say "height" for tables. Properties can be of any type, including nested objects, depending on what your base class is. Say stage in AS3 is a property of any DisplayObject which is used to get the only Stage object there is at runtime[1]. Methods are what any object of a class can be told to do. Say, bombs fall, explode, MovieClips can be told to stop(), etc. You write class code keeping in mind that all of the objects of this class will have to behave exactly like you've written, but since they can differ in properties, you can give them conditional behavior. For example, if a bomb has already exploded, it cannot blow up once more.
A variable, whether a property or a standalone var (if you declare one in a function) is a reference to an object of a given type. Say var i:int refers to some kind of an integer. Simple type vars are containers instead, that is, i=2; will place a 2 in the referred integer, and i=j; will copy the value from j into i, while var theStage:Stage=this.stage will instead create a reference to an existing object, and if that object will change, the reference will give you the changed object instead of its previous state. An array is a collection of variables, reachable by indexes, in AS3 they don't have to be of one type, and a Vector is a typed array.
Next, the lifetime of objects. An object only lives while there's an active reference to it, whether in a property of another alive object, or in a visible variable, or in an event listener (AS3 specific). An object is created via new ClassName(<parameters>), lives while you can reach it somehow, and is destroyed once you have no active links to it and Flash player decides to run garbage collector. Prior to this programmers had to deallocate objects themselves, a rudiment can be seen at BitmapData.dispose(). So, in order to avoid Flash player to run out of free memory, take full control over creation and destruction of links. You don't need to care for simple vars, they are being cared for by Flash player.
A lot of basic functions for interactions has already been implemented in AS3, look for them and examples of how they work in Adobe's manual, navigate through packages in the lower left, most of the interactive stuff is in flash.display package, refer to other packages as necessary.
Hope this will bring you some insight into programming basics.
[1]: There is one stage unless you're doing a load of an SWF, then there could be more, one Stage per one SWF.

Resources