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