Well, I was messing around trying to get something to work. It was 'compiled' code, so I wasn't commenting it. I'll comment it below.
The basic trick is to use a trigger pointing to a custom method as your function pointer. Arguments and results are stored in globals. The 'main' argument has to be stored indirectly, because the caller does not know what type of instance owns the method it is trying to call. In the example it is stored as an array index, and the custom method knows which array to access. So:
A delegate is an indirect 'pointer' to an instance (a Dynamic) and a trigger
Callers store pointer to instance (an array index or bank key) in a common global
Callers store arguments in globals, based on their types
Callers execute the associated trigger once they have stored the argument data
Triggers point to compiler generated methods
The generated methods retrieve the instance pointer and uses it to get the instance
The generated methods retrieve the other arguments out of globals, based on their types
The generated methods pass those values into the intended function
The generated methods stor the result in a global, chosen based on its expected type
Callers pull the result from the result global, chosen based on the expected type
The 'Dynamic' code I posted is the machinery necessary to store the indirect pointers to instances. It ensures the indirect pointers don't outlive what they point to and that they count as a reference to the instance.
class C { int i; }
int F() {
C x1 = new C(0);
Dynamic x2 = Dynamic.From(x1);
C x3 = x2.As(C);
int x4 = x3.i;
return x4;
}
//////////////////////// Compiled Code ////////////////
void Halt(string message) {
int i;
TriggerDebugOutput(0, StringToText(message), true);
i = 1 / 0;
}
int[1] args_int;
//=== Dynamic Class ===
trigger[100] Dynamic_field_Cleaner; // called just before a Dynamic is destroyed (target stored in args_int[0])
int[100] Dynamic_field_Type; // the type of object pointed to by the Dynamic instance
int[100] Dynamic_field_Pointer; // points to the object, assuming you know the type-specific access stuff like which arrays contain the fields
int[100] Dynamic_refs; // reference count
int[100] Dynamic_alloc_list; // linked list of de-allocated instances, '0' is end of list
int Dynamic_alloc_count = 0; // total number of allocated instances
int Dynamic_constructor(int instance, int type, trigger cleaner) {
// allocate an unused index
int me = Dynamic_alloc_list[0];
Dynamic_alloc_count += 1;
if (me == 0) { me = Dynamic_alloc_count; }
Dynamic_alloc_list[0] = Dynamic_alloc_list[me];
// initialize values
Dynamic_field_Type[me] = type;
Dynamic_field_Pointer[me] = instance;
Dynamic_field_Cleaner[me] = cleaner;
Dynamic_refs[me] = 0;
return me;
}
/// Casts the Dynamic to the given type (note: in practice may compile to multiple methods based on pointer type)
int Dynamic_method_As(int me, int type) {
if (type != Dynamic_field_Type[me]) { Halt("Type Mismatch"); }
return Dynamic_field_Pointer[me];
}
void Dynamic_destructor(int me) {
// Fire the 'being destroyed' event, allowing holder to cleanup uncounted reference
if (Dynamic_field_Cleaner[me] != null) {
args_int[0] = me;
TriggerExecute(Dynamic_field_Cleaner[me], true, true);
Dynamic_field_Cleaner[me] = null;
}
// de-allocate index
Dynamic_alloc_count -= 1;
Dynamic_alloc_list[me] = Dynamic_alloc_list[0];
Dynamic_alloc_list[0] = me;
}
void Dynamic_GainRef(int me) {
Dynamic_refs[me] += 1;
}
void Dynamic_LoseRef(int me) {
Dynamic_refs[me] -= 1;
if (Dynamic_refs[me] == 0) { Dynamic_destructor(me); }
}
//==== C Class ====
int[100] C_dyn; // lazily-initialized cached 'dynamic instance' pointing to the C instance
int[100] C_field_i; // value stored in C
int[100] C_refs; // reference count
int[100] C_alloc_list; // linked list of available C indexes ('0' is end of list)
int C_alloc_count = 0; // number of allocated C instances
int C_constructor(int i) {
// allocate index
int me = C_alloc_list[0];
C_alloc_count += 1;
if (me == 0) { me = C_alloc_count; }
C_alloc_list[0] = C_alloc_list[me];
// initialize values
C_field_i[me] = i;
C_refs[me] = 0;
C_dyn[me] = 0;
return me;
}
void C_destructor(int me) {
// Destroy any associated dynamic instance
if (C_dyn[me] != 0) {
Dynamic_field_Cleaner[C_dyn[me]] = null; // prevent re-entrant destroy
Dynamic_destructor(C_dyn[me]);
C_dyn[me] = 0;
}
//de-allocate index
C_alloc_count -= 1;
C_alloc_list[me] = C_alloc_list[0];
C_alloc_list[0] = me;
}
void C_GainRef(int me) {
C_refs[me] += 1;
}
void C_LoseRef(int me) {
C_refs[me] -= 1;
if (C_refs[me] == 0) { C_destructor(me); }
}
// event catcher that forwards C's dynamic instance being destroyed to C losing a reference and nulling its cached dynamic
trigger C_dyn_cleaner = TriggerCreate("C_dyn_cleaner_impl");
bool C_dyn_cleaner_impl(bool b1, bool b2) {
int dyn = args_int[0];
int me = Dynamic_field_Pointer[dyn];
if (C_dyn[me] != 0) {
C_dyn[me] = 0;
C_LoseRef(me);
}
return true;
}
// 'Casts' a C instance to a Dynamic instance by creating a cached Dynamic and returning it
int C_method_ToDynamic(int me) {
if (C_dyn[me] == 0) {
C_dyn[me] = Dynamic_constructor(me, 1, C_dyn_cleaner);
C_GainRef(me);
}
return C_dyn[me];
}
// ===== F method =====
int F() {
int x1;
int x2;
int x3;
int x4;
int r;
x1 = C_constructor(23);
C_GainRef(x1);
x2 = C_method_ToDynamic(x1);
Dynamic_GainRef(x2);
x3 = Dynamic_method_As(x2, 1);
C_GainRef(x3);
x4 = C_field_i[x3];
r = x4; // result
// variables go out of scope
C_LoseRef(x1);
Dynamic_LoseRef(x2);
C_LoseRef(x3);
return r;
}
I was going through the documentation and I noticed there was no support for inheritance. There's also no support for closures. I also noticed that delegates compile into If-Else sequences, instead of something constant time.
I have a method to do all of these things efficiently. At least, assuming a trigger execute is efficient...
The first thing we need is a reasonable 'Dynamic' type. Dynamic stores a pointer to another type, in this case an array index, and the type that the pointer corresponds to. I created a manual compilation example, where a class with reference counting is cast to Dynamic and back:
class C
field i as int
end class
func F() as int
val x1 = new C(0)
val x2 = Dynamic.From(x1)
val x3 = x2.As(C)
val x4 = x3.i
return x4
-- compiles to --
void Halt(string message) {
int i;
TriggerDebugOutput(0, StringToText(message), true);
i = 1 / 0;
}
int[1] args_int;
trigger[100] Dynamic_field_Cleaner;
int[100] Dynamic_field_Type;
int[100] Dynamic_field_Pointer;
int[100] Dynamic_refs;
int[100] Dynamic_alloc_list;
int Dynamic_alloc_count = 0;
int Dynamic_constructor(int instance, int type, trigger cleaner) {
int me = Dynamic_alloc_list[0];
Dynamic_alloc_count += 1;
if (me == 0) { me = Dynamic_alloc_count; }
Dynamic_alloc_list[0] = Dynamic_alloc_list[me];
Dynamic_field_Type[me] = type;
Dynamic_field_Pointer[me] = instance;
Dynamic_field_Cleaner[me] = cleaner;
Dynamic_refs[me] = 0;
return me;
}
int Dynamic_method_As(int me, int type) {
if (type != Dynamic_field_Type[me]) { Halt("Type Mismatch"); }
return Dynamic_field_Pointer[me];
}
void Dynamic_destructor(int me) {
if (Dynamic_field_Cleaner[me] != null) {
args_int[0] = me;
TriggerExecute(Dynamic_field_Cleaner[me], true, true);
Dynamic_field_Cleaner[me] = null;
}
Dynamic_alloc_count -= 1;
Dynamic_alloc_list[me] = Dynamic_alloc_list[0];
Dynamic_alloc_list[0] = me;
}
void Dynamic_GainRef(int me) {
Dynamic_refs[me] += 1;
}
void Dynamic_LoseRef(int me) {
Dynamic_refs[me] -= 1;
if (Dynamic_refs[me] == 0) { Dynamic_destructor(me); }
}
int[100] C_dyn;
int[100] C_field_i;
int[100] C_refs;
int[100] C_alloc_list;
int C_alloc_count = 0;
int C_constructor(int i) {
int me = C_alloc_list[0];
C_alloc_count += 1;
if (me == 0) { me = C_alloc_count; }
C_alloc_list[0] = C_alloc_list[me];
C_field_i[me] = i;
C_refs[me] = 0;
return me;
}
void C_destructor(int me) {
if (C_dyn[me] != 0) {
Dynamic_field_Cleaner[C_dyn[me]] = null;
Dynamic_destructor(C_dyn[me]);
C_dyn[me] = 0;
}
C_alloc_count -= 1;
C_alloc_list[me] = C_alloc_list[0];
C_alloc_list[0] = me;
}void C_GainRef(int me) {
C_refs[me] += 1;
}
void C_LoseRef(int me) {
C_refs[me] -= 1;
if (C_refs[me] == 0) { C_destructor(me); }
}
trigger C_dyn_cleaner = TriggerCreate("C_dyn_cleaner_impl");
bool C_dyn_cleaner_impl(bool b1, bool b2) {
int dyn = args_int[0];
int me = Dynamic_field_Pointer[dyn];
C_dyn[me] = 0;
C_LoseRef(me);
return true;
}
int C_method_ToDynamic(int me) {
if (C_dyn[me] == 0) {
C_dyn[me] = Dynamic_constructor(me, 1, C_dyn_cleaner);
C_GainRef(me);
}
return C_dyn[me];
}
int F() {
int x1;
int x2;
int x3;
int x4;
int r;
x1 = C_constructor(23);
C_GainRef(x1);
x2 = C_method_ToDynamic(x1);
Dynamic_GainRef(x2);
x3 = Dynamic_method_As(x2, 1);
C_GainRef(x3);
x4 = C_field_i[x3];
r = x4;
C_LoseRef(x1);
Dynamic_LoseRef(x2);
C_LoseRef(x3);
return r;
}
Although I used reference counting here, it works just as well with manual destruction. The semantics here are a bit tricky, because destroying the underlying instance automatically destroys the Dynamic instance but 'destroying' the Dynamic instance at best only decreases the underlying instance's reference count.
Now that we have dynamic, we can implement delegates pretty easily. A delegate is a function pointer with its first argument optionally specified. When the delegate is invoked, cast the target instance to dynamic and store it in a global. The 'function pointer' is implemented as a trigger that calls a helper method. The helper method passes the globals into the desired method, and stores the result in a global. Then control returns to the caller, who gets the result from the global.
Example of reducing delegates to code using dynamic:
class Vector
val x as int
val y as int
func Dot(other as Vector) as int
return me.x * other.x + me.y * other.y
end class
func G() as int
Vector v = new Vector(1, -2)
Func(Vector, int) f = v.Dot
return f.Invoke(new Vector(3, 4))
-- simplifies to --
class Func_Vector_int
field static _inst as Dynamic
field static _arg1 as Vector
field static _res as int
field Pointer as trigger
field Inst as Dynamic
func Invoke(arg1 as Vector)
mytype._inst = me.inst
mytype._arg1 = arg1
TriggerExecute(me.Pointer, true, true)
return mytype._res
field static Vector_Dot_Trig = TriggerCreate("Func_Vector_int___Vector_Dot_Impl")
func static Vector_Dot_Impl(bool checkConditions, bool runActions) as bool
val inst = _inst.As(Vector)
_res = inst.Dot(_arg1)
return true
end class
func G() as int
val v = new Vector(1, -2)
val f = new Func_Vector_int(v.AsDynamic(), Func_Vector_int.Vector_Dot_Trig)
return f.Invoke(new Vector(3, 4))
Now that we have delegates, interfaces are trivial. An interface is just a class with delegate members. An instance of class C is cast to interface D by creating delegates for each of C's methods implementing D's functionality.
Rollback Post to RevisionRollBack
To post a comment, please login or register a new account.
@SBeier:
Well, I was messing around trying to get something to work. It was 'compiled' code, so I wasn't commenting it. I'll comment it below.
The basic trick is to use a trigger pointing to a custom method as your function pointer. Arguments and results are stored in globals. The 'main' argument has to be stored indirectly, because the caller does not know what type of instance owns the method it is trying to call. In the example it is stored as an array index, and the custom method knows which array to access. So:
The 'Dynamic' code I posted is the machinery necessary to store the indirect pointers to instances. It ensures the indirect pointers don't outlive what they point to and that they count as a reference to the instance.
I was going through the documentation and I noticed there was no support for inheritance. There's also no support for closures. I also noticed that delegates compile into If-Else sequences, instead of something constant time.
I have a method to do all of these things efficiently. At least, assuming a trigger execute is efficient...
The first thing we need is a reasonable 'Dynamic' type. Dynamic stores a pointer to another type, in this case an array index, and the type that the pointer corresponds to. I created a manual compilation example, where a class with reference counting is cast to Dynamic and back:
Although I used reference counting here, it works just as well with manual destruction. The semantics here are a bit tricky, because destroying the underlying instance automatically destroys the Dynamic instance but 'destroying' the Dynamic instance at best only decreases the underlying instance's reference count.
Now that we have dynamic, we can implement delegates pretty easily. A delegate is a function pointer with its first argument optionally specified. When the delegate is invoked, cast the target instance to dynamic and store it in a global. The 'function pointer' is implemented as a trigger that calls a helper method. The helper method passes the globals into the desired method, and stores the result in a global. Then control returns to the caller, who gets the result from the global.
Example of reducing delegates to code using dynamic:
Now that we have delegates, interfaces are trivial. An interface is just a class with delegate members. An instance of class C is cast to interface D by creating delegates for each of C's methods implementing D's functionality.