[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

RE: [MiNT] Clean AES



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