Hi All, > responses. My proposal basically comes back to the probably very first and > original idea behind an AES: namely to consider it as a set of resident > libraries ("resident" since we don't have a decent scheme of shared > libraries), rather than a process of its own. The actual hardcore > functions, like the trap handler and the code that generates mouse, > keyboard and timer events, would have to be trurly resident and loaded at I'm sorry Konrad but just for the record, Mario Becroft, Steve Sowerby and I proposed this years ago for the Fenix AES and I have mailed that specification to quite a few people around the world. Those that used to hang out on the Fenix developer list should be able to back me up. I have attached that specification along with an implementation of FOOPY which provide a OOP model to most programming languages and that I use for some project and would be a great base for a new AES object scheme. Attached is also a very early specification of FOOPY which is a bit outdated. The AES specification is also slightly outdated. Best regards Sven
Fenix AES overview by Mario Becroft <mb@tos.pl.net> Sven Karlsson <sven@it.lth.se> Steve Sowerby <ssowerby@oxmol.co.uk> NOTE: RFC stands for Request For Comment and means that you are free to answer/respond to that question whatever. NOTE: All other comments/suggestions are naturally also welcome. Goals The purpose and goal of this Fenix' subproject is to make a extendable and modern windowsystem for Fenix based on AES. It will be compatible with AES 4.0 and also have MagiC's and other AES ones extensions. This new AES will be/have: 1) compatible with AES 4.0 and have all extensions of other AESes. 2) faster than or equivally fast as the competing AES. 3) have virtual workspaces like the one in X window managers. 4) a new improved and expanded object type which makes it possible to add new AES object as well as treat a AES object as a standalone OO entity. 5) a new calling scheme based on shared library function calls instead of the old trap based scheme. Roadmap The first version will be monochrome and only support AES 3.20's functions. Design Introduction The Fenix AES is divided in two parts: 1) Server which handles all cliprects, mouse and keyboard input as well as the original AES inter proc communication. It also draws the mouse pointer. 2) The AES library which does all drawing and other AES application services. Why is the AES divided? The AES is divided so that it can be run as a shared library/object in the calling application's memory space without having to use any ugly memory protection overrides. The AES library draws all objects , menu bars and windows from within the calling applications context. This also makes it possible to run whatever windowmanager you want ontop of the server. What does the server do? The server handles all AES keyboard and mouse input also it handles something called "screen areas" and "screens". A screen area is a rectangular area on the screen. The screen areas are registered by the AES library to the server which then manages them and build clip rectangle lists from them. Screen areas are equivalent to AES windows and are used by the library to draw windows. Each window corresponds to one screen area. Each library can have a arbitrary number of registered screen areas. And each screen area acts like a AES window and can be topped etc. Screen areas can be ordered hierarchially. Screens are equivalent to the screen in AmigaOS ie a representation of a full screen in a defined size and at a defined depth. Each screen area can be moved between screens and can also be in several screens at once. Only one screen at a time can normally be shown. The user can change between screens in order to use multiple workspaces. Each screen can have a different size and depth. The library can also register an arbitrary number of mouse rectangles similar to ordinary AES mouse rectangles to each screen area. A mouse rectangle can be used to easily and efficiently keep track of the mouse pointer without polling. Communication between the server and the AES libraries are kept at an absolute minimum since synchronisation cost. Messages are sent from the server to the library when a mouse rectagle is activated, a key is pressed or a AES message is received. The library can also get the current mouse position and state as well as keyboard state by reading readonly global memory allocated by the server. The clip rectangles are sent from the server to the library in buffers in memory that are read only for the library and read-write for the server. Each screen area has it's own buffer. Each buffer is mutexed by a monitor. All other communication to and from the server are done by messages. The server also have a small database over the name of the connected clients. This can be used the library to build the "info"-menu of the menubar. It also keeps track of the currently active client. The clients can share ownership of a couple of screen areas per screen which can be used for menu bars etc. These global screen areas are owned by the currently active client (corresponding to the active application in AES). When the currenly active client is changed both the old and new client will be notifyed. A global semaphore is allocated by the server which can be used by the clients to enforce sole ownership of the current screen if needed. What doesn't the server do? The server doesn't draw anything to the screen besides the mouse cursor. It also doesn't handle timeouts for the client library. If the client needs to use timeouts (ie for evnt_multi) it would have to use the kernel's normal timeouts. This is to avoid the communication overhead caused by applications that polls by using a very small timeout value to evnt_multi. What does the library do? The library provides a simple interface to the application. It also does all drawing that are normally performed by the AES ie window border, menu bar and dialogue refresh etc. It implements all AES functions that now are in a normal AES. Unlike the old AESes FenixAES is accessed using shared library functions ie not traps. New AES object format The old AES object format was/is limited. There is no easy way to add new objectypes and altering the object tree isn't easy either. I propose the following new object format: It is based on FOOPY and thus the type field is no longer needed since each object type corresponds to a proper class. The object are hierarcly organised in a tree like in the old system. Instead of using 16-bits index as pointer in the chains proper 32-bit pointers are used. The size and position fields as well as some of the flags are still used and the position is relative to the father object. This new object struct is meant to be extended with whatever data a object may need. Old AES object[tree]s can be encapsulated in this new scheme using a special compatibility class where the objects have a pointer to the old type objecttree. NOTE: FOOPY will also make it possible to make classes public in libraries. This makes object embedding possible. By using shadow classes a application can also look like a object to another application if that is needed. The scheme will also handle the cases when a base class grows. the proposed new object struct is as follows (c syntax): typedef struct aes_object { struct object *ob_head; /* -> head of object's children */ struct object *ob_tail; /* -> tail of object's children */ uint16 ob_flags; /* flags */ uint16 ob_state; /* state */ int16 ob_x; /* upper left corner of object */ int16 ob_y; /* upper left corner of object */ int16 ob_width; /* width of obj */ int16 ob_height; /* height of obj */ } AES_OBJECT; This struct is used as a base class for own objects. The class has one accellerated function. void redraw(FENIX_BASE_CLASS *this_class,void *this_object,GRECT *redraw_area) This redraw callback can be used to awoid the switch overhead when redrawing. RFC: What other methods are often used besides redraw? RFC: What methods are needed to get a base AES class that can adapt itself to different layouts. Ideally all AES GUI componets should adapt themself to the current windows extents. This can be done using max and minimum extents and vertical and horizontal importance weights. It should also be capable to do automatic layout,resize and redraw. To guide in these investigations the following documentation can be used: developer docs to MUI 3.8 (amiga GUI system based on BOOPSY a sister system to FOOPY) OPENSTEP class tree docs Java API docs This new object format should be used to make the implementation of the AES library OO and easy to maintain. It also makes it possible to use OO abstractions in virtually any programming language.
FOOPY - Fenix Obejct Orientation Programming sYstem - Spec 0.97beta (pronounced foo-pie) Sven Karlsson <sven@it.lth.s> Steve Sowerby <ssowerby@oxmol.co.uk> Goals: The goals of FOOPY is to enable all Fenix programmers access to a simple object oriented system based on standard C-type structs and function pointers. This makes it possible to use OO in all languages that supports calling of either C or assembly routines. It also makes it possible to use OO in assembly. It is based on the BOOPSY system used in AmigaOS. Object Orientation (OO): Object Orientation is a programming paradigm based on the old ADT (Abstract Data Type) paradigm. The two major abstractions in OO is classes and objects. A class is a extension to the ADT (abstract data type) abstraction. It bundles data (objects) together with functions operating on the data (methods). A method is basically a function that is called with the object (data) as a parameter. This way the user of a class only need to know the interfaces to the methods not the organisation or implementation of the data or data type. Coarse implementation: Each object is like a normal C type struct. At the base of each object is a link and a class pointer. These members are pseudo hidden and lie on the negaive side of the object pointer: NOTE: The programmer normally doesn't need to know about these definitions. typedef struct base_object { struct base_object *next; struct base_object *prev; struct class_struct *my_class; } FENIX_BASE_OBJECT; Methods are called with parameters in memory. Embedded in these arguments is the method_id,which uniquely identifies a method, of the method to use. Usually all the arguaments are built on the stack and then a pointer to the stack is calculated by the calling macros. The args are thus represented by a struct: typedef struct oo_args { uint32 method_id; }; This is the minimum argument struct. It can be extended as wished. Naturally the number of parameters is only limited by memory. The class is represented by a struct: typedef struct base_class { int32 __REGARGS (*dispatcher)(struct base_class *class, void *object, struct oo_args *args); /* a0.l is class, d0.l is object, a1.l is args */ struct base_class *super_class; /* pointer to the super class to this class */ struct base_class *next; /* next class in the class list */ void *class_variables: /* pointer to class specific data that can be used for class variables. */ uint32 inst_off; /* offset to object data for this class's data */ uint16 inst_size; /* size of this class's object data */ unit16 acc_off; /* offset into virtual function table for this class accelerated functions */ uint16 acc_count; /* number of accellerated functions used by this class */ uint16 subclass_count; /* number of direct subclasses */ uint16 instance_count; /* number of objects that are instanced from this class */ uint16 flags; } FENIX_BASE_CLASS; At the negative side of the struct lies a table with hook functions that can be used for "accellerated" functions ie when using the dispatcher might be too slow. One example of this kind of function is the redraw method. example: (memory organisation) acc hook N acc hook N-1 . . . acc hook 1 acc hook 0 (class struct) <-+ this is where the class pointer in the object struct points. dispatcher hook | etc | | | and the object: | next pointer | prev pointer | class pointer ------->--/ (object) <------------- this is where a object pointer points data of subclass 0 data of subclass 1 . . data of subclass N-1 data of subclass N The dispatcher is a function which can be used to run a arbitrary method on a object. Each method is identified by method_id. A dispatcher roughly looks like this: int32 My_dispatcher(FENIX_BASE_CLASS *this_class, void *this_object, uint32 *method_id) { switch(*method_id) { case AES_RESIZE: { /* got a resize.. call the resize method */ My_resize(this_object,method_id+1); /* the method_id is a 32-bit integer */ break; } . . /* more methods */ . default: { /* I don't know of this method. let the superclass handle it */ invoke_super_method(this_class,this_object,method_id); } } } All operations on the objects (even attibute put/get) is done through methods which can be called through the dispatcher or directly through acc hooks. Object deletion and creation is also done through methods and the actual new and delete is done by the base class. Since all attributes are accessed through special put/get methods the base class can perform notifies. That works this way: When a attibute that can be used as a trigger for a notify is put (ie altered) the value is changed and a put method is passed on to the superclass until it reach the base class. The base class can then search (for instance a hash) for notifies registered on the attribute. The registry holds a object to be notified and a attribute to be "put". The base class turns puts the original value into the registred object's attribute. This way one can control for instance a meter with a control without the meter/control knowing which control/meter is used, and without writing code to make the objects interact. Binding: To interface to FOOPY C primitives are used. (void *) CLASS_DATA(class) Returns a pointer to class instance variables (void *) INST_DATA(class,obj) Returns a pointer to the class class' instance data of object obj. (these are only for the implementor of methods) (int32) invoke_method(obj,args) (int32) invoke_super_method(class,obj,args) (int32) invoke_method_basic(class,obj,args) (int32) invoke_acc_method(obj,acc_nr,args) It is up to the compiler/assember to choose how to implement these primitives ie if they should be macros or functions. The order and type of the arguments should be preserved though. It is also possible to extend these primitives for instance to allow the programmer to build the arguments on the stack and automatically get the pointer to the arguments using varargs. What these functions do in essence is: { struct base_class *class=((FENIX_BASE_OBJECT *)obj-1)->my_class; /* this is only for method invokes that do not have the class as a parameter */ class=class->super_class; /* only for invoke_super_method */ *(class->dispatcher)(class,obj,args) /* call the method */ } The INST_DATA is essentially a single expression: ((void *)obj+((FENIX_BASE_OBJECT *)class)->inst_off) and CLASS_DATA is (((FENIX_BASE_OBJECT *)class)->class_variables) Tags Methods often use tags to make it possible to expand the argument list. A tagitem is essentially a uint32 coupled with a 32-bit value: struct tagitem { uint32 tag; int32 value; }; Several items can be combined in vectors. The last item's tag is zero (0). Each argument has a unique tag. This makes it possible to for example set several object attributes simultainously. Methods on the base class The base class has some important methods that all are accessable from all classes. Their method_ids are: OM_SET This method is used to set possibly several attribute of an object. In FOOPY each attribute in a specific class tree branch has a unique id just like the methods. binding: class: the object's class obj: the object args: points to a struct: struct { uint32 method_id; /* = OM_SET */ struct tagitem[1]; /* an abitrary number of tags each describing the attribute to be set and its new value. The tag is the attribute id and the value is the new value */ } return value: void OM_GET This method is used to get the value of a specified attribute of an object. binding: class: the object's class obj: the object args: points to a struct: struct { uint32 method_id; /* = OM_GET */ struct tagitem[1]; /* a tag describing the attribute to be inquried and the memory location where the result is to be put. The tag is the attribute id and the value is the memory location */ } return value: 0 if the attribute could not be found nonzero otherwise. OM_NEW New is normally a special function in OOP systems or even builtin the OOP language. In FOOPY however it is a method like any other. This makes it possible to initialize the object before usage exactly like the C++ constructors. The method binding is somewhat peculiar though: binding: class: the object's class obj: the same as the class. args: points to a struct: struct { uint32 method_id; /* = OM_NEW */ struct tagitem[1]; /* an abitrary number of tags each describing a parameter to the contructor and its value. The tag is the parameter id and the value is the new value. Each parameter to the constructor has it's own unique id. */ } return value: a pointer to the created object or NULL(=0) if the object could not be created. OM_DELETE Like object creation deletion is also done through a method. This makes it for instance possible to deallocate allocated resources used by the object before deletion similar to the destructor in C++. binding: class: the object's class obj: the object to be deleted args: points to a uint32 (=OM_DELETE) return value: void OM_FIND_CLASS This method is used to get the class pointer to a class. Each module etc that makes some classes public announce a special class broker object to the FOOPY system. When a class is being searched for each of these brokers are asked and the first one having a class whose name match the searched class's returns the class' pointer. This makes it possible to make classes public to the system. binding: class: the base class obj: pointer to a string containing the class name of the class whose pointer is being inquired. args: points to a uint32 (=OM_FIND_CLASS) OM_NEW_CLASS Classes can be constructed just like objects however the creation process needs a couple more parameters than objects'. binding: class: pointer to a pointer to the class's dispatch obj: pointer to the class's super class. args: points to a struct: struct { uint32 method_id; /* = OM_NEW_CLASS */ uint16 inst_size; /* the size of the instace data needed by the class */ uint16 acc_func; /* number of accellerated functions */ struct tagitem[1]; /* an abitrary number of tags each describing a parameter to the contructor and its value. The tag is the parameter id and the value is the new value. Each parameter to the constructor has it's own unique id. */ } return value: a pointer to the created class or NULL(=0) if the class could not be created. OM_DELETE_CLASS This method deletes a class. It might not be possible to delete the class right away since object might be instanced from it. In that case the class will be tagged for deletion and deleted when the last instanced object is deleted. binding: class: the class obj: the class args: points to a uint32 return value: void System binding: The system reside in a shared library. When the library is opened by an application the root class is returned. From this class it is possible to access public classes, make new classes and also make them public. Scope: The system can be used for pretty much all types of coarse grain OO ie GUI components, module interfaces etc. The different classes can grow independently from each other. The system is not optimized for speed and thus is shouldn't be used for fine grain OO but it is fast enough to be used for "AES objects" without degrading system performance.
Attachment:
FOOPY.c
Description: Binary data
Attachment:
FOOPY.h
Description: Binary data