.\" Copyright (c) 2001-2023 Julien Nadeau Carriere . .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistribution of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED .\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, .\" INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES .\" (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR .\" SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, .\" STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING .\" IN ANY WAY OUT OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" .Dd December 21, 2022 .Dt AG_OBJECT 3 .Os Agar 1.7 .Sh NAME .Nm AG_Object .Nd agar object system .Sh SYNOPSIS .Bd -literal #include .Ed .Sh DESCRIPTION The Agar object system provides object-oriented programming capabilities (inheritance and virtual functions) as well as serialization, managed timers, abstracted data types and a virtual filesystem (VFS). .Pp Agar objects are organized in a tree structure, which we refer to as the VFS. Any .Nm can become the root of a VFS. A VFS can be made persistent to the degree required by the application. Agar objects can be serialized to a machine-independent binary format with .Xr AG_DataSource 3 . .Sh OBJECT INTERFACE .nr nS 1 .Ft "AG_Object *" .Fn AG_ObjectNew "AG_Object *parent" "const char *name" "AG_ObjectClass *classInfo" .Pp .Ft "void" .Fn AG_ObjectInit "AG_Object *obj" "AG_ObjectClass *classInfo" .Pp .Ft "void" .Fn AG_ObjectAttach "AG_Object *newParent" "AG_Object *child" .Pp .Ft "void" .Fn AG_ObjectDetach "AG_Object *child" .Pp .Ft "void" .Fn AG_ObjectMoveToHead "AG_Object *obj" .Pp .Ft "void" .Fn AG_ObjectMoveToTail "AG_Object *obj" .Pp .Ft "void" .Fn AG_ObjectMoveUp "AG_Object *obj" .Pp .Ft "void" .Fn AG_ObjectMoveDown "AG_Object *obj" .Pp .Ft "void" .Fn AG_ObjectDelete "AG_Object *obj" .Pp .Ft "AG_Object *" .Fn AG_ObjectRoot "AG_Object *obj" .Pp .Ft "AG_Object *" .Fn AG_ObjectParent "AG_Object *obj" .Pp .Ft "AG_Object *" .Fn AG_ObjectFind "AG_Object *vfsRoot" "const char *format" "..." .Pp .Ft "AG_Object *" .Fn AG_ObjectFindS "AG_Object *vfsRoot" "const char *name" .Pp .Ft "AG_Object *" .Fn AG_ObjectFindParent "AG_Object *obj" "const char *name" "const char *type" .Pp .Ft "AG_Object *" .Fn AG_ObjectFindChild "AG_Object *obj" "const char *name" .Pp .Ft "char *" .Fn AG_ObjectGetName "AG_Object *obj" .Pp .Ft "int" .Fn AG_ObjectCopyName "AG_Object *obj" "char *buf" "AG_Size bufSize" .Pp .Ft "void" .Fn AG_ObjectSetName "AG_Object *obj" "const char *fmt" "..." .Pp .Ft "void" .Fn AG_ObjectSetNameS "AG_Object *obj" "const char *name" .Pp .Ft "void" .Fn AG_ObjectGenName "AG_Object *obj" "AG_ObjectClass *classInfo" "char *name" "AG_Size nameSize" .Pp .Ft "void" .Fn AG_ObjectGenNamePfx "AG_Object *obj" "const char *prefix" "char *name" "AG_Size nameSize" .Pp .Fn AGOBJECT_FOREACH_CHILD "AG_Object *child" "AG_Object *parent" "TYPE type" .Pp .nr nS 0 The .Fn AG_ObjectNew function allocates and initializes a new object instance of the class described by .Fa classInfo . The object is attached to .Fa parent (unless .Fa parent is NULL). If insufficient memory is available, or .Fa parent and .Fa name are both specified and a child object of the given name already exists then .Fn AG_ObjectNew fails and return NULL. .Pp .Fn AG_ObjectInit initializes an already-allocated instance of the class described by .Fa classInfo . It invokes the .Fn init method over each class in the inheritance hierarchy. .Pp The .Fa name argument to .Fn AG_ObjectNew and .Fn AG_ObjectInit may be NULL, in which case a unique name (e.g., "object0") will be generated. .Fa name may not exceed .Dv AG_OBJECT_NAME_MAX characters and must not contain .Sq / . The .Fa classInfo argument must point to an initialized .Ft AG_ObjectClass structure (see .Sx OBJECT CLASSES ) . .Pp .Fn AG_ObjectAttach attaches an object .Fa child to another object .Fa newParent , raising an .Sq attached event. It's a no-op if .Fa newParent is NULL. .Pp .Fn AG_ObjectDetach removes an object .Fa child from its parent (if any), cancelling scheduled .Xr AG_Timer 3 expirations before raising .Sq detached . .Pp .Fn AG_ObjectMoveUp , .Fn AG_ObjectMoveDown , .Fn AG_ObjectMoveToHead and .Fn AG_ObjectMoveToTail move the object in the parent object's list of child objects. This is useful when the ordering of objects is important. .Pp .Fn AG_ObjectDelete calls .Fn AG_ObjectDetach followed by .Fn AG_ObjectDestroy . .Pp .Fn AG_ObjectRoot returns a pointer to the root of the VFS which the given object is attached to. .Fn AG_ObjectParent returns the object's parent. .Pp The .Fn AG_ObjectFind function returns the object corresponding to the specified path name. If there is no such object it returns NULL. .Pp .Fn AG_ObjectFindParent returns the first ancestor of the object matching either the name of the object (if .Fa name is non-NULL) or the class of the object (if .Fa type is non-NULL). .Pp .Fn AG_ObjectFindChild searches the child objects directly under .Fa obj for an object called .Fa name . It returns a pointer to the object if found or NULL. .Pp .Fn AG_ObjectGetName returns an autoallocated string containing the full pathname of an object (relative to the root of its VFS). If insufficient memory is available to construct the path, it fails and returns NULL. .Pp .Fn AG_ObjectCopyName copies the object's full pathname (relative to its VFS root) to a fixed-size buffer .Fa buf of size .Fa bufSize in bytes. Components of the path are separated by the path separator .Dv AG_PATHSEP ("/" or "\\"). The returned pathname is guaranteed to begin and to end with the path separator. .Fn AG_ObjectCopyName returns 0 on success or -1 if .Fa buf is too small to contain the complete path. .Pp .Fn AG_ObjectSetName updates the name of the given object. If the object is attached to a VFS then the VFS must be locked. .Pp .Fn AG_ObjectGenName generates a unique name for a child object of .Fa obj . The class name (converted to lowercase) is used as prefix followed by an integer (starting from 0 and counting up). The generated string is copied to the fixed-size buffer .Fa name of size .Fa nameSize in bytes. The .Fn AG_ObjectGenNamePfx variant generates a name using the specified prefix instead of the class name. .Pp The .Fn AGOBJECT_FOREACH_CHILD macro iterates .Fa child over every child object of .Fa parent . The .Fa child pointer is cast to .Fa type . In Debug builds, an object validity and class-membership test is performed. .Sh OBJECT CLASSES .nr nS 1 .Ft "void" .Fn AG_RegisterClass "AG_ObjectClass *classInfo" .Pp .Ft "void" .Fn AG_UnregisterClass "AG_ObjectClass *classInfo" .Pp .Ft "AG_ObjectClass *" .Fn AG_CreateClass "const char *classSpec" "AG_Size objectSize" "AG_Size classSize" "Uint major" "Uint minor" .Pp .Ft AG_ObjectInitFn .Fn AG_ClassSetInit "AG_ObjectClass *cl" "AG_ObjectInitFn fn" .Pp .Ft AG_ObjectResetFn .Fn AG_ClassSetReset "AG_ObjectClass *cl" "AG_ObjectResetFn fn" .Pp .Ft AG_ObjectDestroyFn .Fn AG_ClassSetDestroy "AG_ObjectClass *cl" "AG_ObjectDestroyFn fn" .Pp .Ft AG_ObjectLoadFn .Fn AG_ClassSetLoad "AG_ObjectClass *cl" "AG_ObjectLoadFn fn" .Pp .Ft AG_ObjectSaveFn .Fn AG_ClassSetSave "AG_ObjectClass *cl" "AG_ObjectSaveFn fn" .Pp .Ft AG_ObjectEditFn .Fn AG_ClassSetEdit "AG_ObjectClass *cl" "AG_ObjectEditFn fn" .Pp .Ft "void" .Fn AG_DestroyClass "AG_ObjectClass *cl" .Pp .Ft "void" .Fn AG_RegisterNamespace "const char *name" "const char *prefix" "const char *url" .Pp .Ft "void" .Fn AG_UnregisterNamespace "const char *name" .Pp .Ft "AG_ObjectClass *" .Fn AG_LookupClass "const char *classSpec" .Pp .Ft "AG_ObjectClass *" .Fn AG_LoadClass "const char *classSpec" .Pp .Ft "void" .Fn AG_RegisterModuleDirectory "const char *path" .Pp .Ft "void" .Fn AG_UnregisterModuleDirectory "const char *path" .Pp .Ft "int" .Fn AG_OfClass "AG_Object *obj" "const char *pattern" .Pp .Ft "char *" .Fn AG_ObjectGetClassName "const AG_Object *obj" "int full" .Pp .Ft "AG_ObjectClass *" .Fn AG_ObjectSuperclass "const AG_Object *obj" .Pp .Ft "int" .Fn AG_ObjectGetInheritHier "AG_Object *obj" "AG_ObjectClass **pHier" "int *nHier" .Pp .Fn AGOBJECT_FOREACH_CLASS "AG_Object *child" "AG_Object *parent" "TYPE type" "const char *pattern" .Pp .nr nS 0 The .Fn AG_RegisterClass function registers a new object class. .\" MANLINK(AG_Class) .\" MANLINK(AG_Classes) .\" MANLINK(AG_ObjectClass) .Fa classInfo should be an initialized .Ft AG_ObjectClass structure: .Bd -literal .\" SYNTAX(c) typedef struct ag_object_class { char hier[AG_OBJECT_HIER_MAX]; /* Full inheritance hierarchy */ AG_Size size; /* Size of instance structure */ AG_Version ver; /* Version numbers */ void (*init)(void *obj); void (*reset)(void *obj); void (*destroy)(void *obj); int (*load)(void *obj, AG_DataSource *ds, const AG_Version *ver); int (*save)(void *obj, AG_DataSource *ds); void *(*edit)(void *obj); } AG_ObjectClass; .Ed .Pp New methods (and other class-specific data) can be added by overloading .Ft AG_ObjectClass . For example, .Ft AG_WidgetClass adds GUI-specific methods: .Bd -literal .\" SYNTAX(c) typedef struct ag_widget_class { struct ag_object_class _inherit; void (*draw)(void *); void (*size_request)(void *, AG_SizeReq *); /* ... */ } AG_WidgetClass; .Ed .Pp The first field .Va hier describes the full inheritance hierarchy. The string "Agar(Widget:Button)" (or "AG_Widget:AG_Button") says that .Ft AG_Button is a direct subclass of .Ft AG_Widget . .Pp If a class requires dynamically loadable modules (see .Xr AG_DSO 3 ) , the list of modules can be indicated in the .Va hier string by appending "@" and a comma-separated list of library names. For example: .Bd -literal -offset indent "AG_Widget:MY_Widget@myLib,myOtherLib" .Ed .Pp The .Va size member specifies the size in bytes of the object instance structure. .Va ver is an optional datafile version number (see .Xr AG_Version 3 ) . .Pp The .Fn init method initializes a new object instance. It's called by .Fn AG_ObjectInit (and .Fn AG_ObjectNew after a successful allocation). .Pp The .Fn reset method is an optional cleanup routine. It's called by .Fn AG_ObjectLoad before .Fn load and by .Fn AG_ObjectDestroy before .Fn destroy . .Pp The .Fn destroy method frees all resources allocated by the object. .Pp The .Fn load method reads the serialized state of object .Fa obj from data source .Fa ds . .Fn save saves the state of .Fa obj to data source .Fa ds . .Fn load and .Fn save must both return 0 on success or -1 on failure. See .Xr AG_DataSource 3 and the .Sx SERIALIZATION section. .Pp .Fn edit is an application-specific method. In a typical Agar GUI application .Fn edit may generate and return an .Xr AG_Window 3 or an .Xr AG_Box 3 . .Pp .Fn AG_UnregisterClass removes the specified object class. .Pp .Fn AG_CreateClass provides an alternative interface to the passing of a statically-initialized .Ft AG_ObjectClass structure to .Fn AG_RegisterClass . .Fn AG_CreateClass allocates and initializes an .Ft AG_ObjectClass structure (or derivative thereof). .Fn AG_ClassSetInit , .Fn AG_ClassSetReset , .Fn AG_ClassSetDestroy , .Fn AG_ClassSetLoad , .Fn AG_ClassSetSave and .Fn AG_ClassSetEdit set the function pointer for the respective method (returning the previous one). .Pp .Fn AG_DestroyClass unregisters and frees an auto-allocated .Ft AG_ObjectClass (or derivative thereof). .Pp .Fn AG_RegisterNamespace registers a new namespace with the specified name, prefix and informational URL. For example, Agar registers its own using: .Bd -literal -offset indent .\" SYNTAX(c) AG_RegisterNamespace("Agar", "AG_", "https://libagar.org/"); .Ed .Pp Once the namespace is registered, it is possible to specify inheritance hierarchies using the partitioned .Em namespace format: .Bd -literal -offset indent Agar(Widget:Button):MyLib(MyButton) .Ed .Pp which is equivalent to the conventional format: .Bd -literal -offset indent AG_Widget:AG_Button:MY_Button .Ed .Pp .Fn AG_UnregisterNamespace deletes the specified namespace. .Pp The .Fn AG_LookupClass function searches for the .Ft AG_ObjectClass structure corresponding to the given class The .Fa classSpec string can be in conventional or partitioned namespace format (see .Fn AG_RegisterNamespace ) . If the search is unsuccessful, it returns NULL. .Pp .Fn AG_LoadClass ensures that the class specified by the .Fa classSpec string is registered, possibly loading dynamic libraries if needed. If the class description string includes libraries (e.g., "@lib1,lib2") then the registered modules directories (see .Fn AG_RegisterModuleDirectory ) will be scanned and the needed modules loaded automatically. .Pp Library names in class description strings should be bare, without prefix or suffix (the actual filename on disk being platform-dependent). Valid libraries are loaded via .Xr AG_DSO 3 . The first library must define a .Sq myFooClass symbol (where .Sq myFoo is the name of the class transformed from .Sq MY_Foo ) , which should be a pointer to the .Ft AG_ObjectClass describing the object class. .Pp .Fn AG_UnloadClass unregisters the specified class and also decrements the reference count of any dynamically-located module associated with it. If this reference count reaches zero, the module is removed from the current process's address space. .Pp The .Fn AG_RegisterModuleDirectory function adds the specified directory to the module search path. .Fn AG_UnregisterModuleDirectory removes the specified directory from the search path. .Pp The .Fn AG_OfClass function tests whether .Fa obj is an instance of the class described by .Fa pattern and returns 1 if the object belongs to that class. .Fa pattern is a class description string which may include "*" wildcards. For example, "AG_Widget:AG_Button:*" would match the "AG_Button" class or any subclass thereof. The pattern "AG_Widget:AG_Button" would match only the "AG_Button" class but not its subclasses. Fast paths are provided for patterns such as "Super:Sub:*" and "Super:Sub", but general patterns such as "Super:*:Sub:*" are also supported. .Pp .Fn AG_ObjectGetClassName returns a newly-allocated string containing the name of the class of an object .Fa obj . If .Fa full is 1, return the complete inheritance hierarchy (e.g., "AG_Widget:AG_Button"). Otherwise, return only the subclass (e.g., "AG_Button"). .Pp .Fn AG_ObjectSuperclass returns a pointer to the class description structure of the superclass of .Fa obj . If .Fa obj is an instance of the base .Nm class then the base class (i.e., .Va agObjectClass returned). .Pp The .Fn AG_ObjectGetInheritHier function returns into .Fa pHier an array of .Ft AG_ObjectClass pointers describing the inheritance hierarchy of an object. The size of the array is returned into .Fa nHier . If the returned item count is > 0, the returned array should be freed when no longer in use. .Fn AG_ObjectGetInheritHier returns 0 on success or -1 if there is insufficient memory. .Pp The .Fn AGOBJECT_FOREACH_CLASS macro iterates .Fa child over every child object of .Fa parent which is an instance of the class specified in .Fa pattern . The .Fa pattern argument is a class description string (that may include "*"). The .Fa child variable is cast to .Fa type with type checking in Debug builds (and no checking in Release builds). .Sh SERIALIZATION .nr nS 1 .Ft "int" .Fn AG_ObjectLoad "AG_Object *obj" .Pp .Ft "int" .Fn AG_ObjectLoadFromFile "AG_Object *obj" "const char *file" .Pp .Ft "int" .Fn AG_ObjectLoadFromDB "AG_Object *obj" "AG_Db *db" "const AG_Dbt *key" .Pp .Ft "int" .Fn AG_ObjectLoadData "AG_Object *obj" .Pp .Ft "int" .Fn AG_ObjectLoadDataFromFile "AG_Object *obj" "const char *file" .Pp .Ft "int" .Fn AG_ObjectLoadGeneric "AG_Object *obj" .Pp .Ft "int" .Fn AG_ObjectLoadGenericFromFile "AG_Object *obj" "const char *file" .Pp .Ft "int" .Fn AG_ObjectSave "AG_Object *obj" .Pp .Ft "int" .Fn AG_ObjectSaveAll "AG_Object *obj" .Pp .Ft "int" .Fn AG_ObjectSaveToFile "AG_Object *obj" "const char *path" .Pp .Ft "int" .Fn AG_ObjectSaveToDB "AG_Object *obj" "AG_Db *db" "const AG_Dbt *key" .Pp .Ft "int" .Fn AG_ObjectSerialize "AG_Object *obj" "AG_DataSource *ds" .Pp .Ft "int" .Fn AG_ObjectUnserialize "AG_Object *obj" "AG_DataSource *ds" .Pp .Ft "int" .Fn AG_ObjectReadHeader "AG_DataSource *ds" "AG_ObjectHeader *header" .Pp .Pp .nr nS 0 These functions implement serialization, or archiving of the state of an .Nm to a flat, machine-independent binary format. .Pp The .Fn AG_ObjectLoad* family of functions load the state of an Agar object from some binary data source. The generic .Nm part of the object is loaded first, followed by any class-specific serialized data (which is read by invoking the .Fn load function over every class in the inheritance hierarchy). .Pp The .Fn AG_ObjectLoad , .Fn AG_ObjectLoadGeneric and .Fn AG_ObjectLoadData functions look for an archive file in the default search path (using the .Dv AG_CONFIG_PATH_DATA of .Xr AG_Config 3 ) . .Pp The .Fn AG_ObjectLoadFromFile , .Fn AG_ObjectLoadGenericFromFile and .Fn AG_ObjectLoadDataFromFile variants attempt to load the object state from a specific file. The .Fn AG_ObjectLoadFromDB variant loads the object state from the given .Xr AG_Db 3 database entry. .Pp The .Fn AG_ObjectSave* family of functions serialize and save the state of the given object. The generic .Nm state is written first, followed by the object's serialized data (which is written by invoking the .Fn save function of every class in the inheritance hierarchy). .Pp .Fn AG_ObjectSave creates an archive of the object in the default location (the .Dv AG_CONFIG_PATH_DATA of .Xr AG_Config 3 ) . The .Fn AG_ObjectSaveAll variant saves the object's children as well as the object itself. .Fn AG_ObjectSaveToFile archives the object to the specified file. .Fn AG_ObjectSaveToDB archives the object to the given .Xr AG_Db 3 entry. .Pp The .Fn AG_ObjectSerialize function writes an archive of the given object to the specified .Xr AG_DataSource 3 , and .Fn AG_ObjectUnserialize reads an archive of the given object. .\" MANLINK(AG_ObjectHeader) .Pp The .Fn AG_ObjectReadHeader routine attempts to read the header of a serialized Agar object from a .Xr AG_DataSource 3 and returns 0 on success or -1 if no valid header could be read. On success, header information is returned into the .Fa header structure: .Bd -literal .\" SYNTAX(c) typedef struct ag_object_header { char hier[AG_OBJECT_HIER_MAX]; /* Inheritance hierarchy */ char libs[AG_OBJECT_LIBS_MAX]; /* Library list */ char classSpec[AG_OBJECT_HIER_MAX]; /* Full class string */ Uint32 dataOffs; /* Dataset offset */ AG_Version ver; /* AG_Object version */ Uint flags; /* Object flags */ } AG_ObjectHeader; .Ed .Sh FINALIZATION .nr nS 1 .Ft "void" .Fn AG_ObjectDestroy "AG_Object *obj" .Pp .Ft void .Fn AG_ObjectReset "AG_Object *obj" .Pp .Ft "void" .Fn AG_ObjectFreeEvents "AG_Object *obj" .Pp .Ft "void" .Fn AG_ObjectFreeVariables "AG_Object *obj" .Pp .Ft "void" .Fn AG_ObjectFreeChildren "AG_Object *obj" .Pp .Ft "void" .Fn AG_ObjectFreeChildrenOfType "AG_Object *obj" "const char *pattern" .Pp .Ft "void" .Fn AG_ObjectFreeChildrenOfTypeLockless "AG_Object *obj" "const char *pattern" .Pp .nr nS 0 .Fn AG_ObjectReset restores the state of an object to some initial state. It invokes the object's .Fn reset , which is expected to bring the object to a consistent state prior to deserialization (before .Fn load ) . .Pp .Fn AG_ObjectDestroy frees all resources allocated by an object. It invokes the .Fn reset and .Fn destroy methods over each class in the inheritance hierarchy. .Fn AG_ObjectDestroy also cancels any scheduled .Xr AG_Timer 3 expiration. .Fn AG_ObjectDestroy implies .Fn AG_ObjectFreeEvents , .Fn AG_ObjectFreeVariables and .Fn AG_ObjectFreeChildren . Unless .Dv AG_OBJECT_STATIC is set, .Fn AG_ObjectDestroy also implies .Xr free 3 . .Pp .Fn AG_ObjectFreeEvents clears all configured event handlers (also cancelling any scheduled timer expirations). .Pp .Fn AG_ObjectFreeVariables clears the .Xr AG_Variable 3 table of the object. .Pp The .Fn AG_ObjectFreeChildren routine invokes .Fn AG_ObjectDetach and .Fn AG_ObjectDestroy over every child object under .Fa parent . .Pp .Fn AG_ObjectFreeChildrenOfType invokes .Fn AG_ObjectDetach and .Fn AG_ObjectDestroy on every child object under .Fa parent which is an instance of the class described by .Fa pattern (see .Xr AG_OfClass 3 ) . The .Fn AG_ObjectFreeChildrenOfTypeLockless variant assumes .Fa obj is locked. .Sh THREADS .nr nS 1 .Ft "void" .Fn AG_ObjectLock "AG_Object *obj" .Pp .Ft "void" .Fn AG_ObjectUnlock "AG_Object *obj" .Pp .Ft "void" .Fn AG_LockVFS "AG_Object *obj" .Pp .Ft "void" .Fn AG_UnlockVFS "AG_Object *obj" .Pp .Ft "void" .Fn AG_ObjectDetachLockless "AG_Object *child" .Pp .Ft "void" .Fn AG_ObjectFreeChildrenLockless "AG_Object *obj" .Pp .nr nS 0 .Fn AG_ObjectLock and .Fn AG_ObjectUnlock acquire or release the locking device associated with the given object. This is a mutex protecting all read/write members of the .Nm structure, except .Fa parent , .Fa root and the list of child objects .Fa cobjs which are all considered part of the virtual filesystem and are instead protected by .Fn AG_LockVFS . .Pp The .Fn AG_ObjectLock mutex can be used as a general-purpose locking device which is guaranteed to be held during processing of all events posted to the object as well as during object method such as .Fn load and .Fn save . .Pp .Fn AG_LockVFS and .Fn AG_UnlockVFS acquire or release the lock which protects the layout of the entire VFS which .Fa obj is a part of. .Pp Agar is compiled without threads support ("--disable-threads") then .Fn AG_ObjectLock , .Fn AG_ObjectUnlock , .Fn AG_LockVFS and .Fn AG_UnlockVFS are no-ops. .Pp The .Fn AG_ObjectDetachLockless call is a variant of .Fn AG_ObjectDetach which assumes that parent and child are locked. .Pp The .Fn AG_ObjectFreeChildrenLockless call is a variant of .Fn AG_ObjectFreeChildren which assumes that parent and child are locked. .Sh FLAGS The following public .Nm flags are defined: .Bl -tag -width "AG_OBJECT_INDESTRUCTIBLE " .It AG_OBJECT_INDESTRUCTIBLE Application-specific advisory flag. .It AG_OBJECT_STATIC Object is statically allocated (or allocated via a facility other than .Xr malloc 3 ) . Disable use of .Xr free 3 by .Fn AG_ObjectDestroy . .It AG_OBJECT_READONLY Application-specific advisory flag. .It AG_OBJECT_DEBUG Application-specific debugging flag. .It AG_OBJECT_NAME_ONATTACH Automatically generate a unique name for the object as soon as .Fn AG_ObjectAttach occurs. .El .Sh EVENTS The .Nm mechanism generates the following events: .Bl -tag -width 2n .It Fn attached "AG_Object *parent" The object has been attached to a new parent. .It Fn detached "AG_Object *parent" The object has been detached from its parent. .It Fn renamed "void" The object's name has changed. .It Fn object-post-load "const char *path" Invoked by .Fn AG_ObjectLoadData , on success. If the object was loaded from file, .Fa path is the pathname of the file. .It Fn bound "AG_Variable *V" A new variable binding has been created, or the value of an existing binding has been updated; see .Xr AG_Variable 3 for details. .El .Sh STRUCTURE DATA For the .Ft AG_ObjectClass structure (see .Sx OBJECT CLASSES section): .Pp .Bl -tag -compact -width "void (*destroy) " .It Ft char *hier Full inheritance hierarchy. .It Ft AG_Size size Size of instance structure (in bytes). .It Ft AG_Version ver Versioning information (see .Xr AG_Version 3 ) . .It Ft void (*init) Initialization routine. .It Ft void (*reset) Cleanup routine (for .Fn AG_ObjectReset ) . .It Ft void (*destroy) Final cleanup routine. .It Ft int (*load) Deserialization routine. .It Ft int (*save) Serialization routine. .It Ft void *(*edit) Application-specific entry point. .El .Pp The following read-only members are initialized internally: .Pp .Bl -tag -compact -width "TAILQ(AG_ObjectClass) sub " .It Ft char *name The name for this class only. .It Ft char *libs Comma-separated list of DSO modules. .It Ft AG_ObjectClass *super Pointer to the superclass. .It Ft TAILQ(AG_ObjectClass) sub Direct subclasses of this class. .El .Pp For the .Ft AG_Object structure: .Bl -tag -width "char name[AG_OBJECT_NAME_MAX] " .It Ft char name[AG_OBJECT_NAME_MAX] Unique (in parent) identifier for this object instance. May not contain .Sq / . .It Ft AG_ObjectClass *cls A pointer to the .Ft AG_ObjectClass for this object's class (see .Sx OBJECT CLASSES ) . .It Ft Uint flags Option flags for this object instance (see .Sx FLAGS section). .It Ft TAILQ(AG_Event) events Table of registered event handlers (set by .Xr AG_SetEvent 3 ) and virtual functions (set by .Fn AG_SetFn ) . .It Ft TAILQ(AG_Timer) timers Active timers (see .Xr AG_Timer 3 ) . .It Ft TAILQ(AG_Variable) vars Named variables (see .Xr AG_Variable 3 ) . .It Ft TAILQ(AG_Object) children Child objects. .El .Sh EXAMPLES The following code initializes a stack-allocated object, prints a message on the debug console and finally destroys the object: .Bd -literal -offset indent .\" SYNTAX(c) AG_Object myObject; AG_ObjectInit(&obj, "myObject", &agObjectClass); Debug(&obj, "Hello, world!\\n"); AG_ObjectDestroy(&obj); .Ed .Pp The following code creates a VFS representing a document, searches for an element by name and finally destroys the VFS: .Bd -literal -offset indent .\" SYNTAX(c) AG_Object *doc, *head, *title, *body, *p; AG_Object *result; doc = AG_ObjectNew(NULL, "doc", &agObjectClass); head = AG_ObjectNew(doc, "head", &agObjectClass); title = AG_ObjectNew(head, "title", &agObjectClass); body = AG_ObjectNew(doc, "body", &agObjectClass); p = AG_ObjectNew(body, "p", &agObjectClass); result = AG_ObjectFindS(doc, "/doc/head/title"); if (result != NULL) { AG_Verbose("Title = %s\\n", result->name); } AG_ObjectDestroy(doc); .Ed .Pp The following code transforms an object's name to upper-case: .Bd -literal -offset indent .\" SYNTAX(c) void ObjectNameToUpper(AG_Object *obj) { char name[AG_OBJECT_NAME_MAX], *c; if (AG_ObjectCopyName(obj, name, sizeof(name)) == -1) { return; } for (c = name; *c != '\\0'; c++) { *c = toupper(*c); } AG_ObjectSetNameS(obj, name); } .Ed .Pp The following code attaches an object to a parent, detaches it and then reattaches it to a different parent: .Bd -literal -offset indent .\" SYNTAX(c) AG_Object parent1, parent2, child; AG_ObjectInit(&parent1, NULL, &agObjectClass); AG_ObjectInit(&parent2, NULL, &agObjectClass); AG_ObjectInit(&child, NULL, &agObjectClass); AG_ObjectAttach(&parent1, &child); AG_ObjectDetach(&child); AG_ObjectAttach(&parent2, &child); .Ed .Pp The following code uses .Dv AGOBJECT_FOREACH_CLASS to iterate .Fa child over every child object of the .Fa parent object which is an instance of "MyClass": .Bd -literal -offset indent .\" SYNTAX(c) struct my_class *chld; AGOBJECT_FOREACH_CLASS(chld, parent, my_class, "MyClass") { printf("Child %s is an instance of MyClass.\\n", AGOBJECT(chld)->name); } .Ed .Pp The following code performs a class-membership test with a pattern: .Bd -literal -offset indent .\" SYNTAX(c) AG_Button *btn = AG_ButtonNew(NULL, 0, NULL); if (AG_OfClass(btn, "AG_Widget:AG_Button")) { /* * btn is an instance of AG_Button, * and NOT a subclass thereof. */ } if (AG_OfClass(btn, "AG_Widget:AG_Button:*")) { /* * btn is an instance of AG_Button, * OR a subclass thereof. */ } .Ed .Pp The following code performs the same class-membership test using the numerical class identifier (as opposed to an .Fn AG_OfClass pattern): .Bd -literal -offset indent .\" SYNTAX(c) AG_Button *btn = AG_ButtonNew(NULL, 0, NULL); if (OBJECT(btn)->cid == AGC_BUTTON) { /* * btn is an instance of AG_Button, * and NOT a subclass thereof. */ } if (AG_BUTTON_ISA(btn)) { /* * btn is an instance of AG_Button * OR a subclass thereof. */ } .Ed .Pp The following code registers a new class "MyClass" and instantiates it: .Bd -literal -offset indent .\" SYNTAX(c) AG_ObjectClass myClass = { "MY_Class", sizeof(AG_Object), { 1,0, 1234, 0xE03A }, NULL, /* init */ NULL, /* reset */ NULL, /* destroy */ NULL, /* load */ NULL, /* save */ NULL /* edit */ }; AG_Object *obj; AG_RegisterClass(&myClass); obj = AG_ObjectNew(NULL, NULL, &myClass); Debug(obj, "Hello, world!\\n"); AG_ObjectDestroy(obj); .Ed .Pp The following code implements a new class "MY_Dummy" which overloads .Nm with new structure members. It also handles serialization and provides a public interface in C. The public header file follows: .Bd -literal -offset indent .\" SYNTAX(c) /* * Public header file for MY_Dummy (my_dummy.h). */ typedef struct my_dummy { struct ag_object _inherit; /* AG_Object -> MY_Dummy */ Uint flags; #define MY_DUMMY_OPT1 0x01 /* Some option */ #define MY_DUMMY_OPT2 0x02 /* Another option */ int x,y; /* Some integers */ void *myData; /* Allocated data */ } MY_Dummy; extern AG_ObjectClass myDummyClass; MY_Dummy *_Nullable MY_DummyNew(int, int, Uint); .Ed .Pp The implementation follows: .Bd -literal -offset indent .\" SYNTAX(c) /* * Implementation of MY_Dummy (my_dummy.c). */ #define MYDATASIZE 1024 MY_Dummy * MY_DummyNew(int x, int y, Uint flags) { MY_Dummy *d; d = AG_TryMalloc(sizeof(MY_Dummy)); if (d == NULL) { return (NULL); } AG_ObjectInit(d, &mdDummyClass); d->x = x; d->y = y; d->flags = flags; return (d); } static void Init(void *_Nonnull obj) { MY_Dummy *d = obj; d->flags = 0; d->x = 0; d->y = 0; d->myData = Malloc(MYDATASIZE); memset(d->myData, 0, MYDATASIZE); } static void Destroy(void *_Nonnull obj) { MY_Dummy *d = obj; free(d->myData); } static int Load(void *_Nonnull obj, AG_DataSource *_Nonnull ds, const AG_Version *_Nonnull ver) { MY_Dummy *d = obj; d->flags = AG_ReadUint8(ds); d->x = (int)AG_ReadUint16(ds); d->y = (int)AG_ReadUint16(ds); return AG_Read(ds, d->myData, MYDATASIZE); } static int Save(void *_Nonnull obj, AG_DataSource *_Nonnull ds) { MY_Dummy *d = obj; AG_WriteUint8(ds, (Uint8)(d->flags)); AG_WriteUint16(ds, (Uint16)d->x); AG_WriteUint16(ds, (Uint16)d->y); return AG_Write(ds, d->myData, MYDATASIZE); } AG_ObjectClass myDummyClass = { "MY_Dummy", sizeof(MY_Dummy), { 1,0, 0,0 }, Init, NULL, /* reset */ Destroy, Load, Save, NULL /* edit */ }; .Ed .Pp The following code maps the "MY_" prefix to a new namespace "MyPackage" and uses it in the class description string .Fa classSpec passed to the .Fn AG_LookupClass function: .Bd -literal -offset indent .\" SYNTAX(c) AG_ObjectClass *C; AG_RegisterNamespace("MyPackage", "MY_", "https://example.com/"); AG_RegisterClass(&myImageViewerClass); C = AG_LookupClass("Agar(Widget):" "MyPackage(ImageViewer)"); if (C != NULL) { AG_Verbose("Found class %s\\n", C->name); AG_Verbose("Structure size = %d\\n", C->size); } .Ed .Pp The following code prints the inheritance hierarchy of an object: .Bd -literal -offset indent .\" SYNTAX(c) void PrintInheritHier(AG_Object *obj) { AG_ObjectClass *hier; int nHier, i; if (AG_ObjectGetInheritHier(obj, &hier, &nHier) != 0) { AG_FatalError(NULL); } AG_Verbose("AG_Object"); for (i = 0; i < nHier; i++) { AG_Verbose(" -> %s", hier[i]->name); } AG_Verbose("\\n"); AG_Free(hier); } .Ed .Pp The following code loads an object from a file, increments a variable .Va counter and writes back the object to the file: .Bd -literal -offset indent .\" SYNTAX(c) AG_Object obj; int counter; AG_ObjectInit(&obj, NULL, &agObjectClass); AG_SetInt(&obj, "counter", 0); if (AG_ObjectLoadFromFile(&obj, "test.obj") == -1) AG_FatalError(NULL); counter = AG_GetInt(&obj, "counter"); AG_Debug(&obj, "Counter: %d -> %d\\n", counter, counter+1); AG_SetInt(&obj, "counter", counter+1); AG_ObjectSaveToFile(obj, "test.obj"); .Ed .Pp The Agar GUI represents user interfaces using a tree of .Xr AG_Widget 3 objects attached to a parent .Xr AG_Window 3 which is itself attached to some parent .Xr AG_Driver 3 . .Pp The .Xr SG 3 scene-graph structure of Agar-SG is a VFS of .Xr SG_Node 3 objects. Non-visible nodes can be paged out to storage, saving memory. .Pp Edacious (https://edacious.hypertriton.com/) represents circuits, components and simulation data using an in-memory VFS. Circuits are saved to a flat binary file which embeds the circuit's serialized data with that of its sub-components (which may include third-party components, in which case .Nm will autoload any required DSOs). .Sh SEE ALSO .Xr AG_Event 3 , .Xr AG_Intro 3 , .Xr AG_Timer 3 , .Xr AG_Variable 3 .Sh HISTORY The .Nm interface appeared in Agar 1.0. .Fn AG_ObjectFreeDataset was renamed .Fn AG_ObjectReset in Agar 1.6.0. The functions .Fn AG_CreateClass , .Fn AG_ClassSetInit , .Fn AG_ClassSetReset , .Fn AG_ClassSetDestroy , .Fn AG_ClassSetLoad , .Fn AG_ClassSetSave , .Fn AG_ClassSetEdit , .Fn AG_DestroyClass and .Fn AG_ObjectGetClassName appeared in Agar 1.6.0.