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
Related
I've recently taken upon myself to add setter and getter methods to my class.
Since doing this, many parts of my code got broken and I'm unable to access getter methods.
Take the example below:
private loadInputs() : Input[] {
var inputs = <Input[]>this.get('inputs');
inputs.sort((a,b) => a.QuoteRef().localeCompare(b.QuoteRef()))
return( inputs || [] );
}
My input class has 2 variables,
_Project: string
_line: string
Which I access using a method QuoteRef()
public QuoteRef(): string {
return this._Project.concat('-' + this._Line.toString().padStart(3,'0'));
}
Whenever I try to access a method or a getter from my class on an item that is casted as an Input, I can see the variables (though not access them as they are private), but the prototype section doesn't contain any of the methods.
This triggers the following error in the website console:
TypeError: a.QuoteRef is not a function
What am I doing wrong?
Update
I got it to work by updating the code as follows:
inputs.sort((a,b) => {
let first = new Input(a);
let second = new Input(b);
return first.QuoteRef().localeCompare(second.QuoteRef());
});
Without seeing your complete class I can only guess, but I think that a and b in your sort are not of the type you expect. I can't see what this.get('inputs') does, but I suspect it is not returning an array with Input class objects. Hence the function cannot be found (is not a function). You could try:
inputs.sort((a,b) => {
console.log(typeof a);
console.log(typeof b);
a.QuoteRef().localeCompare(b.QuoteRef());
})
and check what the type is. Then check what your this.get actually returns.
Edit: forgot to mention that your IDE probably does not warn you because you cast the output of this.get to <Input[]>.
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.
I am attempting to implement a solution from How to set canBecomeKeyWindow? Into my native C application using Objective-C runtime (The app is already written with objective C Runtime). Is there a way to create a subclass purely in Objective-C Runtime?
Right now I just create NSWindow object but need to be able to create my own so I can override the function specified in that question.
objc_msgSend((id)objc_getClass("NSWindow"), sel_registerName("alloc"));
The signature of can_become_key_window_true is slightly incorrect. According to the documentation (https://developer.apple.com/documentation/objectivec/objective-c_runtime/imp?language=objc) the function should have at least two arguments: "self" and "_cmd". So the signature should be like:
static bool can_become_key_window_true(__unused id _self, __unused SEL _cmd) {
return true;
}
You could also use #encode to construct the type encoding for the function.
char encoding[10]; // should be enough
snprintf(encoding, 10, "%s%s%s", #encode(BOOL), #encode(id), #encode(SEL));
... or you could get a method from UIWindow and get its type encoding like:
Method m = class_getInstanceMethod(objc_lookUpClass("UIWindow"), sel_getUid("canBecomeKeyWindow"));
const char *encoding = method_getTypeEncoding(m);
And as you might have noticed you could use sel_getUid() instead of sel_registerName as you expect this selector to be already registered by this time (because you are about to override an existing method).
To allocate a new instance you could use
window = class_createInstance(__UIWindow);
Figured it out after a lot of code searching:
// Subclass NSWindow with overridden function
Class __NSWindow =
objc_allocateClassPair(objc_getClass("NSWindow"), "__NSWindow", 0);
class_addMethod(__NSWindow,
sel_registerName("canBecomeKeyWindow"),
(IMP)can_become_key_window_true, "B#:");
objc_registerClassPair(__NSWindow);
// Allocate a new __NSWindow
window = objc_msgSend((id)__NSWindow, sel_registerName("alloc"));
And then can_become_key_window_true is defined as:
static bool can_become_key_window_true() {
return true;
}
I use objc_allocateClassPair to subclass the object and return a Class of that object. Then I use class_addMethod to override the method canBecomeKeyWindow. And finally use objc_registerClassPair to register my new class before using it as I would a normal NSWindow.
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).
I have an edit page where the user can edit a file in the system, and then save it. When loading the file, I make two objects out of the result, one is bound to the view and the other I wish to keep (in its original state) until "save" is clicked, and then use it to compare vs the view-bound object, to see if any changes have been made.
So, when the page loads, this is being run
$http.get('/api/files/' + $stateParams.id)
.then(function (result) {
vm.fileTemp = result.data;
vm.fileTempCopy = result.data;
The fileTempCopy is not being touched or referenced by anything in the view or elsewhere in the controller, except in the save-method, where i check if they are alike or not. But somehow, both of them are updated when i make changes to the input fields (as if they were both used as ng-model for the inputs).
if(vm.fileTemp === vm.fileTempCopy)//in save-function
is always true, and their fields are exactly the same.
Why does this happen and how can I solve it?
Using the assignment operator, you are actually just referencing the original array. Arrays are reference types. That means, that they don't actually store values, they only store references to those values. What you where doing is copying a reference to a memory location, meaning that any changes to the memory at that location (including removing elements) will be reflected in both arrays.
So you will want to do this instead:
vm.fileTemp = angular.copy(result.data);
vm.fileTempCopy = angular.copy(result.data);
here is a very basic approach to checking an object's "value equality".
function isEquivalent(a, b) {
// Create arrays of property names
var aProps = Object.getOwnPropertyNames(a);
var bProps = Object.getOwnPropertyNames(b);
// If number of properties is different,
// objects are not equivalent
if (aProps.length != bProps.length) {
return false;
}
for (var i = 0; i < aProps.length; i++) {
var propName = aProps[i];
// If values of same property are not equal,
// objects are not equivalent
if (a[propName] !== b[propName]) {
return false;
}
}
// If we made it this far, objects
// are considered equivalent
return true;
}
//After your update Outputs: false
console.log(isEquivalent(vm.fileTemp, vm.fileTempCopy));