TweetFollow Us on Twitter

IAC Driver 2
Volume Number:4
Issue Number:10
Column Tag:Advanced Mac'ing

IAC Driver, Part II

By Frank Alviani, Contributing Editor, Paul Snively, Contributing Editor

Inter-Application Communication Under MutiFinder: The Continuing Saga,

or

Data, Data, Who’s Got the Data?

or

If One Application is the Master and the Other is the Slave, is the IAC Driver a Slave Driver?

Greetings, ladies and gentlemen, and welcome to yet another episode in the latest Macintosh development soap opera. In this segment, we ask the crucial question, “What does it actually take to make this puppy fly?” Luckily, Frank has spent a great deal of time and effort answering that question to his own satisfaction while I was out dancing with Shellie and Alice in Boston. (We should all be so lucky, I might add.)

Seriously, here is our attempt at cleaning up some of the mysteries surrounding how this driver we’ve written actually works. The definitive answer to that whole question can, of course, only be ascertained by careful examination of the source code, a herculean task at best.

Rather than put you, gentle reader, through that, here is our distillation of both what we wrote last month, and what is in the source code that accompanies this article (which is supposed to be reasonably short, since the code is far larger than anything MacTutor has ever published before).

Disclaimer!! This version of the IAC Driver is ß1!! We have had enough requests and inquiries to convince us to go ahead and release this early Beta version for people to experiment with, understanding that revisions are inevitable. THIS DRIVER UNDOUBTEDLY STILL HAS A FEW BUGS IN IT! Don’t commit all your eggs to the IAC basket just yet!!

First of all, the driver had to be remarkably transparent (read: drop it in your System Folder and forget that it exists). For that reason it is a Startup Document (for you pre-System 6.0 freaks, it’s an INIT file). Basically there’s a ridiculously simple little INIT in there that opens the DRVR in the system heap, makes sure it’s in the unit table, and then makes sure that it won’t go away when the INIT file is closed (by detaching the resource). Peter Helme of Mac DTS kindly provided a small chunk of code that finds the highest unoccupied slot in the unit table and renumbers the DRVR resource to ensure it is loaded there.

Once the driver is there, applications are free to check to see if it’s around and, if it is, to make use of it. The calls were documented last time as to name and function (sort of), so I’m going to try to focus on some of the not-so-obvious aspects of what goes on, and why (Frank is actually explaining the “why”; it’s his design, after all).

First of all, understand one important fact: inter-application communication under MultiFinder (or any other multitasking environment, for that matter) is neither more nor less than an exercise in memory management. The source application has to allocate storage for the transfer of data to the target application, and somehow the target application has to be made aware that the block of memory has something that it wants. Also, since the source data can be changed in a fairly dynamic manner relative to the target retrieving the data, you have to keep track of various versions or “editions” of the data somehow. All of this is the driver’s responsibility. It allocates and releases the storage (yes, Paul Sydney, it “does” release the storage), and it tracks editions as necessary.

A second matter to consider during program design is that inter-application communication is very much like network communication (only without the messy wires) and therefore similar problems arise, such as timing and coordination difficulties. If you’ve never dealt with those before, it’s going to prove very stressful for a while (although the problems aren’t really of the same magnitude).

Specification changes

Since the time the protocol was published in the August 1988 issue of MacTutor, a few small changes were found to be necessary in the specifications. Here they are:

1) The identifier for data types was changed from a 16-bit integer to the 4-character resource type used by everybody else (no, I don’t recall the good reason I had for choosing an integer in the first place).

2) The information returned by the Census call for an extent now includes the latest edition, since that is necessary for the application to be able to determine if it needs to read that extent or not.

3) When an extent is deleted by its source, the entry is removed from the table entirely (rather than just setting the edition to -1 as originally stated). Targets that try to access that extent in the future will get a “No such extent” error from the driver, and will know to not try to read it anymore (the extent should NOT be purged from the application’s internal tables, since re-opening the document in the future will re-establish the dependency).

Internal Data Structures

One of the keys to understanding any piece of code is a good grasp of the data structures it uses: how they are used and why they were chosen. The data structures used in the IAC driver are not terribly complex.

The extent table - is the heart of the driver. It is a fixed-length table that includes information about where an extent originates, who is interested in it, and what is the latest version of data that each target has read.

Since the entry for an extent is a fixed size structure, the entries for each edition are kept in a linked list. Editions are added to the list in chronological order, with the newest edition closest to the extent entry. Each edition in turn is a variable-size structure (the size of an extent’s data can of course vary, and the number of formats written to the driver can vary for each edition) and is therefore also a linked list. The data format identifier for each chunk of data is stored in the block with the data as a 32-bit header.

The interest mask - is used to identify targets for a source extent. The position in the document-ID table serves as a bit number in this mask (i.e. slot 2 is bit 2 in the mask). In the extent’s entry in the master table, the bits serve as identifiers of the target documents, and are only turned off by a target document severing a link. In the mask stored with each edition, the bits serve to record which documents have not yet read the data, and are turned off as soon as a target has read an edition. The read routine checks the interest mask for an edition after returning the data, and deletes the edition if no interested targets remain.

The document-ID table - is a table of unique document IDs. Every entry in this table is one of three types - Empty (zero), Target Document (1), or Source Document (a negative number). It is critical for every document interacting with the IAC driver to appear in this table, since the slot_ID is used to access the interest mask for extent entries; yet, document IDs are only assigned to source documents (a document that is only a target of others does not need to be identified by other than its slot_ID) - how to resolve this?

Target-only documents are those that do not pass a document ID to the “Complete Dependency” call; a dummy entry is put into the table to uniquely identify them for this session, and a slot_ID is assigned. Later, if they make an “Add Dependency” call and pass the existing slot_ID in (as they must under the protocol), the slot_ID is used to put the new document ID in the table, replacing the dummy entry. We take advantage of the fact that all date/times returned by the system are negative numbers to make this easy to process.

These data structures were designed for simplicity; they contain the minimum amount of information that will allow the driver to do its job. They do not, for example, maintain any information about the “boundaries” of extents, since that is highly application specific and impossible to anticipate; also, maintaining that material in the driver would result in excessive traffic between the IAC driver and applications.

A Routine Commentary

Most of the stuff from last time that concentrated on the interface specifications was explained fairly well, and I don’t wish to copy it here. Instead, I will provide my commentary on things that were perhaps not that clear. I’ll go routine by routine.

Open - it turns out to be impossible to check for MultiFinder at boot time, when this driver is opened. Therefore, it is the responsibility of the application to check and refuse to try and support IAC operations when MultiFinder isn’t running. The ‘C’ glue code provided does this check during the open call, so you shouldn’t need to.

AddDependency --What’s hard about this? Not much! Basically what it does is to add a new entry to the driver’s internal tables that are used to keep track of data that is being made available to interested applications. This is called by the source application.

There are 2 ways in which a source application can make this call: with a default document ID of zero, or with existing document and slot IDs (the driver has no way of checking up on you, and assumes that non-zero IDs are valid). The default case is easy - just give the document it’s IDs and send it home. If IDs are provided, however, life becomes complicated - there is the possibility of collisions with slot IDs already registered. If that occurs, a new slot ID is assigned and returned to the caller - this is why the slot ID returned from this call MUST ALWAYS be the one used in further communications with the driver.

A reminder that this call does NOT pass any data to the driver; an explicit “Write Data” call must be made.

CompleteDependency --This one is called by the “target” application, not the source! It tells the driver “hey, I’m interested in whatever’s available right now.” It basically lets the driver set up the “Interested mask” for the dependency properly. I’ll say a bit more about the “Interested mask” later.

RemoveDependency --This one is a bit trickier, as it can be called by either the source application or the target. If it’s called by the source, all editions of the data that have been posted get totally wiped, and the extent itself is deleted so that any targets that were interested get informed that the data has been blasted out from under them (they’ll get a “No such dependency” error). On the other hand, it may be a single target that’s calling. If that is the case, the appropriate bit in the “Master Interest mask” (and all editions) gets reset (that’s one less suitor, darling). When no one cares at all anymore (that is, the entire “Master Interest mask” goes to zero), the driver will blow away all of the editions just as it would if the source removed the dependency. Got all that? There’s a quiz next month.

A reminder that this call does NOT retrieve any data from the driver; an explicit “Read Data” call must be made.

AvailableDependency --I had a truly bad pun for this one, but I’ll resist the temptation. This is easy. It just makes the extent indicated by the parameters the source to all future defaulted “CompleteDependency”s until another dependency is made the available one. This has the highly desirable side-effect of making it trivial to have more than one target interested in the same source.

Status --Here’s a simple one, too. It just tells whomever is calling how many documents have been registered with the driver, and how many extents that exist are relevant to the document asking for the status (have editions that the target hasn’t read yet). Its real raison d’etre, though, is to tell the caller what version of the driver is installed (ahem).

Census --is like a high-powered Status. It doesn’t care who’s making the call. It just blasts out a list of all the extents that it knows of, period.

WriteData --this is rather like using the clipboard. The idea is that you may want to pass data in more than one format. A spreadsheet, for example, may wish to pass both the cell selection (let’s call that type CELL) and a pie chart of the selection (as a PICT). Remember, this call is made by the source after it has established a dependency. The master interest mask is copied from the extent table to the header for the new edition to establish which targets have yet to read the new data.

ReadData -- the target’s analog of WriteData, of course. It’s like SFGetFile inasmuch as it gives a list of preferred data types, in order of desirability. The target application makes this call, passing the most recent edition number of the data that it received last time. If there are one or more editions more recent than that, the driver will return the latest, marking the “Interested mask” for each successive edition as appropriate. It’s conceivable (in fact, all too likely for some applications, such as databases) that the target won’t be calling ReadData sufficiently frequently to get each and every edition that the source application posts, which is why we use editions in the first place. Reading an edition turns off the target’s bit in the edition-interest-mask, and all older editions. An interest mask of zero results in that edition being deleted.

Notes on the code

This was developed on a Mac Plus running MPW 2.0.2 under System 6.0 It should work on any machine running MultiFinder, although the technique used in the glue to determine if MultiFinder is actually running is not authorized by Apple. Their claim that “you should only check for services you need” ignores the fact that in this case the service needed is the multitasking capability. A few other matters...

First, none of the people involved with this project belong to the “Names-cost-a-dollar-a-letter” school of identifiers. Hence, the verbose identifiers and extensive comments. It was hard enough to debug with almost every line commented, believe me!

Second, the INIT resource is set up so that if you have the object to ShowInit by Paul Mercer, you can modify a couple of lines and link it in to display start-up icons. We didn’t have permission to print his source, and don’t like publishing software in MacTutor without every line of source needed to rebuild it, so this seems a workable compromise.

Third, some of the makefiles may have traces of the Odesta development environment showing through, even though I tried to “genericize” them. I hope they won’t be too hard to correct..

Bug Fixes and Updates

Inevitably, bugs will be found in this code, and we already have thought of several desirable improvements to add in the future (after we’re recovered from this exercise in hubris). It is not reasonable to expect Dave Smith and MacTutor to act as the central exchange for this driver, since the effort involved could become significant.

After January 1st, 1989, we will be acting as IAC Central, issuing revised versions as necessary, etc. Bug reports can be sent to us immediately (we’d like to think there will never be any, but...) along with suggestions for improvements. We can be reached by electronic mail (please, no phones calls!!) at the following addresses:

Paul Snively - GEnie: PSNIVELY

(The first line of any letter should be:

ATTN: PSNIVELY PAUL SNIVELY for the mail server he is using)

Frank Alviani - GEnie: TOOLSMITH

Delphi: TOOLSMITH

In addition, US Snail can reach us (slowly) at the following address:

CodeSmiths

P.O. Box 8744

Waukegan, Ill 60079

We will try and respond in a timely fashion, given the constraints of very demanding jobs, a house renovation, and small children.

That about wraps it up for our end. Here -- at long last!-- is the source code for the driver and the glue (in ‘C’) to simplify writing applications to support the protocol!

{1}
Listing:  driver.m
# Makefile for the IAC Driver!
# Frank Alviani  -- 8/88

ob = {hlxEtc}    #Set these definitions to your normal working directories
sr = {hlxSrc}
prog = {hlx}
e  = echo 

“{prog}SAWSInit” ƒ “{ob}SAWSdrvr.lnk”“{ob}SAWSInit”      “{sr}SAWSINIT.r”
 {e} ‘Date -a -t‘ Rez IAC >> “{log}”
 Rez -t INIT -c SAWS -o “{prog}SAWSInit” “{sr}SAWSinit.r”

“{ob}SAWSDRVR.a.o” ƒ “{sr}SAWSDRVR.a”
 {e} ‘Date -a -t‘ Asm SAWSDRVR.a >> “{log}”
 Asm “{sr}SAWSDRVR.a” -i Me:MPW:AIncludes: -o “{hlxetc}”

“{ob}SAWSdrvr.lnk” ƒ “{ob}SAWSDRVR.a.o”
 {e} ‘Date -a -t‘ Link SAWSDRVR.a. >> “{log}”
 Link -t INIT -c SAWS -rt DRVR=31 -sg .IAC “{ob}SAWSDRVR.a.o” 
 -o “{ob}SAWSdrvr.lnk”

“{ob}SAWSINIT.a.o” ƒ “{sr}SAWSINIT.a”
 {e} ‘Date -a -t‘ Asm SAWSINIT.a >> “{log}”
 Asm “{sr}SAWSINIT.a” -i Me:MPW:AIncludes: -o “{hlxetc}”

“{ob}SAWSInit”   ƒ “{ob}SAWSINIT.a.o”
 {e} ‘Date -a -t‘ Link SAWSINIT.a >> “{log}”
 Link -rt INIT=0 -ra =16 “{ob}SAWSINIT.a.o” -o “{ob}SAWSInit”
{2}
Listing:  SysEnv.a
;
;Glue for SysEnvirons that was skipped by Apple
;F. Alviani
;7/88
;

 INCLUDE‘TRAPS.A’;Include Memory manager macros.
 PRINT ON

SYSENVIRONS FUNC  EXPORT
 movea.l 4(sp),a0;a0 contains ptr to world record
 move.w 8(sp),d0 ;d0 contains desired version
 _SysEnvirons
 movea.l(sp)+,a0 ;get return addr, remove
 lea  6(sp),sp ;pop parameters
 move d0,(sp)    ;put result in place
 jmp  (a0);go home

 DC.B ‘SYSENVIR’

 END
{3}
Listing: IAC.h

/* This is the set of externs needed to access the IAC interface routines 
*/
/* The pointer/handle indicators are only reminders */

extern  short  iac_add_dependency(); /* *doc_ID, *slot_ID, *hat_check,
  *edition */
extern  short  iac_available_dependency();   /* doc_id, hat_check */
extern  short  iac_census();/* *extent_count, **extent_info */
extern  short  iac_complete_dependency();    /* *doc_id, *slot_id, *hat_check 
*/
extern  short  iac_open();
extern  short  iac_read_data();    /* doc_id, slot_id, hat_check, *edition,
       fmt_pref[], *fmt_code, **ext_data */
extern  short  iac_remove_dependency();/* doc_id, slot_id, hat_check 
*/
extern  short  iac_status();/* slot_id, *vers_id, *doc_count,
    *extent_count */
extern  short  iac_write_data();   /* doc_id, hat_check, *edition,
    fmt_count, **ext_data */

/* error codes for iac_open, etc. */
# define NO_DRIVER -1
# define EARLY_SYS -2
# define MAX_EXTS64

typedef struct {
 long   doc_ID;
 short  hat_check;
 short  ed_level;
}info_rec;

typedef struct {
 info_rec ext_entry[MAX_EXTS];
} info_tbl, *info_tblP, **info_tblH;
{4}
Listing:  IAC.c

/***
 *
 * File:IAC.c
 *
 * Package: Inter Application Communications
 *
 * Description:  interface package to the driver for the use of
 * application programs.
 *
 * Author(s):
 * FEA  6/19/88
 *
 */

# include <types.h>
# include <files.h>
# include <memory.h>
# include <osutils.h>
# include <serial.h>
# include <iac.h>

# defineMIN_BLK_SIZE 0xC
# defineDEBUG  false

static  short  iac_ref_num;

/**
 * Routine: iac_open
 *
 * The IAC driver is opened by this call and the ioRefNum   
 * saved so it doesn’t need to be passed with all calls.
 *    A null value is returned   if the open is successful;
 * otherwise the operating system error is
 * returned.
 */

short iac_open()

{
SysEnvRec theWorld;
/* ALMOST complete knowledge about the world */
THzs_ZoneP, a_ZoneP; /* so we can check zone adjacency */
OSErr   the_err;

short   result = 0;

 the_err = SysEnvirons(1, &theWorld);
 if (theWorld.systemVersion < 0x0420)/* too early! */
 {
 result = EARLY_SYS; /* “no multifinder” */
 }
 else
 {
 /*check for multifinder active by checking if system zone     
 is adjacent to application zone, which never happens under
 MultiFinder.
 */
 s_ZoneP = SystemZone();
 a_ZoneP = ApplicZone();
 if ( ((long) s_ZoneP->bkLim + MIN_BLK_SIZE) == (long) a_ZoneP )
 {
 result = EARLY_SYS; 
/* zones adjacent - no multifinder */
 }
 else /* now try to actually open the IAC driver */
 {
 SysBeep(1);
 result = OPENDRIVER(“\p.IAC”, &iac_ref_num);
 }
 }

 return(result);
}


/**
 * Routine: iac_add_dependency
 *
 * This is used to inform the IAC driver that a new
 * dependency has been  established and should be 
 *added to its internal tables. A null value is
 * returned if the open is successful; otherwise the operating 
 * system error is returned.
 */

short iac_add_dependency(doc_id, slot_id, hat_check, edition)
 long *doc_id; 
/* identifies the source document (permanent) */
 short  *slot_id;
/* identifies the source document (session) */
 short  *hat_check;/* extent identifier */
 short  *edition;
/* how many times extent has changed (session) */

{
IOParam the_blk;
OSErr   the_err;

struct {
 short  func;
 long doc_id;
 short  slot_id;
 short  hat_check;
 short  edition;
} my_params;

# if DEBUG
 return(the_err=noErr); /* short circuit testing without MF */
# endif

 my_params.func = 1; /* set up private parameter block */
 my_params.doc_id = *doc_id;
 my_params.slot_id = *slot_id;
 my_params.hat_check = *hat_check;

 the_blk.ioCompletion = nil;/* setup driver parametr block */
 the_blk.ioRefNum = iac_ref_num;
 the_blk.ioBuffer = &my_params;
 the_blk.ioReqCount = sizeof(my_params);
 the_blk.ioPosMode = fsFromStart;
 the_blk.ioPosOffset = 0;
 the_err = PBWrite(&the_blk.qLink, false);   
/* add the dependency */

 if (the_err == noErr)    /* update output parameters */
 {
 *doc_id = my_params.doc_id;
 *slot_id = my_params.slot_id;
 *hat_check = my_params.hat_check;
 *edition = my_params.edition;
 }

 return(the_err);
}


/**
 * Routine: iac_complete_dependency
 *
 * This is used to inform the IAC driver of a document that 
 * is interested in a particular dependency. A null value is 
 * returned if the open is successful; otherwise the
 * operating system error is returned.
 */

short iac_complete_dependency(doc_id, slot_id, hat_check)
 long *doc_id; 
/* identifies the source document (permanent) */
 short  *slot_id;
/* identifies the source document (session) */
 short  *hat_check;/* extent identifier */

{
IOParam the_blk;
OSErr   the_err;

struct {
 short  func;
 long doc_id;
 short  slot_id;
 short  hat_check;
} my_params;

# if DEBUG
 return(the_err=noErr);   
/* short circuit for testing without MF */
# endif

 my_params.func = 2; /* set up private parameter block */
 my_params.doc_id = *doc_id;
 my_params.slot_id = *slot_id;
 my_params.hat_check = *hat_check;

 the_blk.ioCompletion = nil;/* setup driver parametr block */
 the_blk.ioRefNum = iac_ref_num;
 the_blk.ioBuffer = &my_params;
 the_blk.ioReqCount = sizeof(my_params);
 the_blk.ioPosMode = fsFromStart;
 the_blk.ioPosOffset = 0;
 the_err = PBWrite(&the_blk.qLink, false);   
/* complete the dependency */

 if (the_err == noErr)    /* update output parameters */
 {
 *doc_id = my_params.doc_id;
 *slot_id = my_params.slot_id;
 *hat_check = my_params.hat_check;
 }

 return(the_err);
}


/**
 * Routine: iac_remove_dependency
 *
 * This is used to inform the IAC driver that a document is 
 * no longer interested in a particular dependency. If the 
 * document is the original source all “targets” will be
 * informed that there is no longer any such
 * extent; if all targets lose interest the source will be 
 * informed that it no longer needs to update the extent.
 */

short iac_remove_dependency(doc_id, slot_id, hat_check)
 long doc_id;
/* identifies the source document (permanent) */
 short  slot_id; 
/* identifies the source document (session) */
 short  hat_check; /* extent identifier */

{
IOParam the_blk;
OSErr   the_err;

struct {
 short  func;
 long doc_id;
 short  slot_id;
 short  hat_check;
} my_params;

# if DEBUG
 return(the_err=noErr); /* short circuit testing without MF */
# endif

 my_params.func = 3; /* set up private parameter block */
 my_params.doc_id = doc_id;
 my_params.slot_id = slot_id;
 my_params.hat_check = hat_check;

 the_blk.ioCompletion = nil; /* setup driver paramtr block */
 the_blk.ioRefNum = iac_ref_num;
 the_blk.ioBuffer = &my_params;
 the_blk.ioReqCount = sizeof(my_params);
 the_blk.ioPosMode = fsFromStart;
 the_blk.ioPosOffset = 0;
 the_err = PBWrite(&the_blk.qLink, false);   
/* remove the dependency */

 return(the_err);
}


/**
 * Routine: iac_available_dependency
 *
 * This sets the indicated extent as the “available extent” 
 *to be usedas the source for future defaulted
 *”complete_dependency”  calls.
 */

short iac_available_dependency(doc_id, hat_check)
 long doc_id;/* identifies the source document (permanent) */
 short  hat_check; /* extent identifier */

{
IOParam the_blk;
OSErr   the_err;

struct {
 short  func;
 long doc_id;
 short  hat_check;
} my_params;

# if DEBUG
 return(the_err=noErr);   
/* short circuit for testing without MF */
# endif

 my_params.func = 4; /* set up private parameter block */
 my_params.doc_id = doc_id;
 my_params.hat_check = hat_check;

 the_blk.ioCompletion = nil;/* setup driver parametr block */
 the_blk.ioRefNum = iac_ref_num;
 the_blk.ioBuffer = &my_params;
 the_blk.ioReqCount = sizeof(my_params);
 the_blk.ioPosMode = fsFromStart;
 the_blk.ioPosOffset = 0;
 the_err = PBWrite(&the_blk.qLink, false);   
/* this dependency is now “available” */

 return(the_err);
}


/**
 * Routine: iac_status
 *
 * This allows the application program to find out what’s 
 * going on.
 */

short iac_status(slot_id, vers_id, doc_count, extent_count)
 short  slot_id; /* identifies the inquiring document (session) */
 short  *vers_id;/* driver version*100 */
 short  *doc_count;/* count of active documents */
 short  *extent_count;  /* count of extents relevant to inquiring doc 
*/

{
IOParam the_blk;
OSErr   the_err;

struct {
 short  func;
 short  slot_id;
 short  vers_id;
 short  doc_count;
 short  extent_count;
} my_params;

# if DEBUG
 return(the_err=noErr); /* short circuit testing without MF */
# endif

 my_params.func = 5; /* set up private parameter block */
 my_params.slot_id = slot_id;

 the_blk.ioCompletion = nil;/* setup driver parametr block */
 the_blk.ioRefNum = iac_ref_num;
 the_blk.ioBuffer = &my_params;
 the_blk.ioReqCount = sizeof(my_params);
 the_blk.ioPosMode = fsFromStart;
 the_blk.ioPosOffset = 0;
 the_err = PBRead(&the_blk.qLink, false);    
/* read IAC status */

 if (the_err == noErr)    /* update output parameters */
 {
 *vers_id = my_params.vers_id;
 *doc_count = my_params.doc_count;
 *extent_count = my_params.extent_count;
 }

 return(the_err);
}


/**
 * Routine: iac_census
 *
 * This provides identifying info for all registered extents.
 */

short iac_census(extent_count, extent_info)
 short  *extent_count;  /* count of extents registered */
 info_tblPextent_info;  /* Ptr to table of info for each extent */

{
IOParam the_blk;
OSErr   the_err;
short   i;

struct {
 short  func;
 short  extent_count;
 info_rec extent_info[MAX_EXTS];
} my_params;

# if DEBUG
 return(the_err=noErr);   /* short circuit for testing without MF */
# endif

 my_params.func = 6; /* set up private parameter block */

 the_blk.ioCompletion = nil;/* set up driver parameter block */
 the_blk.ioRefNum = iac_ref_num;
 the_blk.ioBuffer = &my_params;
 the_blk.ioReqCount = sizeof(my_params);
 the_blk.ioPosMode = fsFromStart;
 the_blk.ioPosOffset = 0;
 the_err = PBRead(&the_blk.qLink, false);    
/* read IAC status */

 if (the_err == noErr)  /* update output parameters */
 {
 *extent_count = my_params.extent_count;
 BlockMove (&my_params.extent_info[0],
    (Ptr) extent_info,
    (long) my_params.extent_count * sizeof(info_rec));
 }

 return(the_err);
}


/**
 * Routine: iac_write_data
 *
 * This updates the data for the specified extent, resulting 
 * in a new change level.
 */

short iac_write_data(doc_id, hat_check, edition, fmt_count, ext_data)
 long doc_id;  /* identifies source document (permanent) */
 short  hat_check; /* extent identifier */
 short  *edition;/* how many times extent has changed (session) */
 short  fmt_count; /* number of formats being written */
 Handle ext_data;/* Handle to actual data */

{
IOParam the_blk;
OSErr   the_err;

struct {
 short  func;
 long doc_id;
 short  hat_check;
 short  edition;
 short  fmt_count;
 Handle the_dataH;
} my_params;

# if DEBUG
 return(the_err=noErr);   /* short circuit for testing without MF */
# endif

 my_params.func = 7; /* set up private parameter block */
 my_params.doc_id = doc_id;
 my_params.hat_check = hat_check;
 my_params.fmt_count = fmt_count;
 my_params.the_dataH = ext_data;

 the_blk.ioCompletion = nil;/* set up driver parameter block */
 the_blk.ioRefNum = iac_ref_num;
 the_blk.ioBuffer = &my_params;
 the_blk.ioReqCount = sizeof(my_params);;
 the_blk.ioPosMode = fsFromStart;
 the_blk.ioPosOffset = 0;
 the_err = PBWrite(&the_blk.qLink, false);   
/* update dependency */

 if (the_err == noErr)    /* update output parameters */
 {
 *edition = my_params.edition;
 }

 return(the_err);
}


/**
 * Routine: iac_read_data
 *
 * This is used to retrieve the actual data for the latest 
 * change_level for the specified extent. The IAC driver will 
 * record that the inquiring document has read the data.
 *
 * ext_data will be resized by the driver to hold the data.
 */

short iac_read_data(doc_id, slot_id, hat_check, edition, fmt_pref,
   fmt_code, ext_data)
 long doc_id;    /* identifies the source document (permanent) */
 short  slot_id; /* identifies the source document (session) */
 short  hat_check; /* extent identifier */
 short  *edition;/* how many times extent has changed (session) */
 long fmt_pref[3]; /* preferred formats, descending desirability */
 long *fmt_code; /* format returned to caller */
 Handle ext_data;/* Handle to actual data */

{
IOParam the_blk;
OSErr   the_err;

struct {
 short  func;
 long doc_id;
 short  slot_id;
 short  hat_check;
 short  edition;
 long fmt_pref[3];
 long fmt_code;
 Handle ext_data;
} my_params;

# if DEBUG
 return(the_err=noErr);   /* short circuit for testing without MF */
# endif

 my_params.func = 8; /* set up private parameter block */
 my_params.doc_id = doc_id;
 my_params.slot_id = slot_id;
 my_params.hat_check = hat_check;
 my_params.edition = *edition;
 my_params.fmt_pref[0] = fmt_pref[0];
 my_params.fmt_pref[1] = fmt_pref[1];
 my_params.fmt_pref[2] = fmt_pref[2];
 my_params.ext_data = ext_data;

 the_blk.ioCompletion = nil;/* set up driver parameter block */
 the_blk.ioRefNum = iac_ref_num;
 the_blk.ioBuffer = &my_params;
 the_blk.ioReqCount = sizeof(my_params);
 the_blk.ioPosMode = fsFromStart;
 the_blk.ioPosOffset = 0;
 the_err = PBRead(&the_blk.qLink, false);    /* read the data */

 if (the_err == noErr)
 {
 *edition = my_params.edition;
 *fmt_code = my_params.fmt_code;
 }

 return(the_err);
}
{5}
Listing:  SAWSDRVR.a
;**************************************************************
;***** The SAWS Inter-Application Communication Driver    *****
;***** Written with blazing speed 6-8/88 by Paul F. Snively  
;***** With one HECK of a lotta help from Frank Alviani     ***
;*****          and Mike Walsh                           *****
;**************************************************************
;
;Modification History: 
;6/19/88 First Draft--1.0D1--Paul F. Snively
;7/10/88 Second draft--1.0B1--Paul F. Snively & Michael R.  ;Walsh
;8/7/88  Yet another crack at it--1.0B1--Paul F. Snively &
; Frank Alviani
;8/8-21/88 Final Debugging -- Frank Alviani
;
;Basically this puppy is the DRVR resource that actually 
;implements Frank’s cool ideas that are documented in the
; article that accompanies this listing.
;

 INCLUDE‘Traps.a’
 INCLUDE‘QuickEqu.a’
 INCLUDE‘SysEqu.a’
 INCLUDE‘SysErr.a’
 INCLUDE‘ToolEqu.a’
 STRING PASCAL

NumOfDocs EQU  32;Max of 32 documents
NumOfExtentsEQU  64
Unimpl  EQU $9F  ;For testing environment
OSDisp  EQU $8F
DummyID EQU 1

NoMoreDocsEQU  -2000 ;This one isn’t used--yet
NoMoreSlots EQU  -2001    ;Neither is this one--yet
WriteFailed EQU  -2002    ;Or this one...
MissingLink EQU  -2003;Yet another SAWS-defined error
NoNewerEdition EQU -2004  ;YASAWSE
ReadFailedEQU  -2005 ;SASAWSE
NoSuchDep EQU  -2006
OldROMs EQU -2007

ExtentRec RECORD 0
DocID DS.L1;Home document for this extent
HatCheckDS.W1    ;Unique ID for this extent
Edition DS.W1    ;The edition number
MstrInterestMsk  DS.L1    ;Master Interest Mask
EditionList DS.L 1 ;Edition List
ExtentSizeEQU  *
 ENDR

EditionHdrRECORD 0
NextEd  DS.L1  ;Handle to next Ed
InterestMaskDS.L 1 ;Mask for this particular Ed
NumFormatsDS.W 1 ;How many formats stored?
EdInstances DS.L 0 ;Instances start here
EditionSize EQU  *
 ENDR

IACGlobalsRECORD 0
ExtentCount DS.W 1 ;Number of extents extant
ExtentTable DS.L NumOfExtents*ExtentRec.ExtentSize
DocIDs  DS.LNumOfDocs;Unique Document identifiers
AvailDependency  DS.L1  ;Offset of currently available dependency
IACGlobalsSize EQU *
 ENDR

DriverEntry PROC ; See Device Manager IM:2
 IMPORT DriverOpen,DriverPrime
 IMPORT DriverCtl,DriverDone
 IMPORT DriverClose

 DC.B (1<<dReadEnable) + (1<<dWritEnable)    ; We handle reads/writes
 DC.B 0 ; Lower byte is unused
 DC.W 0*60; 0 sec periodic update
 DC.W 0 ; We don’t do events
 DC.W 0 ; No menu for this accessory

 DC.W DriverOpen-DriverEntry; Open routine
 DC.W DriverPrime-DriverEntry ; Prime
 DC.W DriverCtl-DriverEntry ; Control
 DC.W DriverDone-DriverEntry; Status - unused
 DC.W DriverClose-DriverEntry ; Close

 DC.B ‘.IAC’;The name of our driver
 ALIGN  2 ; Word align

 ENDPROC

DriverOpenPROC
 IMPORT IACGlobalsSize

 TST.W  ROM85    ;old ROMs? 
 BGE.S  @1;no, keep going?
 MOVE.W #OldROMs,D0
 BRA.S  ExitOpen
@1
 with   IACGlobals
 TST.L  dCtlStorage(A1)   ;Did we already allocate our         
 space?
 BNE.S  ExitOpen ;If so, then we’re already open
 MOVE.L #IACGLobalsSize,D0;How much global space do we         
 want?
 _NewHandle sys,clear;Try to allocate it
 BNE.S  ExitOpen ;Fail miserably since error                   
 occurred
@2
 MOVE.L A0,dCtlStorage(A1);Otherwise store handle
 MOVE.W #0,ExtentCount    ;We start with zero extents
 endwith;IACGlobals

ExitOpen
 RTS    ;We’re outta here
 ENDPROC

DriverClose PROC
 IMPORT WipeExtentData
 MOVE.L dCtlStorage(A1),D0;Get our global handle
 BEQ.S  ExitClose;If we don’t have one, leave!
 MOVEA.LD0,A6    ;Otherwise get into address                   
 register

 with   IACGlobals
 MOVE.W ExtentCount(A6),D7;How many extents are there?
 LEA  ExtentTable(A6),A0  ;Point to the ExtentTable
 MOVE.L A0,D6    ;Get it in the right register
 endwith;IACGlobals

WalkExtents
 MOVEA.LD6,A0    ;Get Extent Pointer
 JSR  WipeExtentData ;Kill its data

NextExtent
 with   ExtentRec
 SUBQ.W #1,D7    ;Decrement extent counter
 BEQ.S  EndClose ;And continue until done
 ADD.L  #ExtentSize,D6    ;Point to next extent
 BRA.S  WalkExtents;And loop
 endwith;ExtentRec

EndClose
 MOVEA.LdCtlStorage(A1),A0;Get our globals handle
 _DisposHandle   ;And get rid of it

ExitClose 
 RTS    ;Back to caller
 ENDPROC

;
; This is the main entry point for ALL functions of the driver
;
DriverPrime PROC
 IMPORT DriverAddDependency,DriverCompleteDependency
 IMPORT DriverRemoveDependency,DriverRemoveDependency
 IMPORT DriverAvailableDependency,DriverStatus
 IMPORT DriverCensus,DriverWriteData
 IMPORT DriverReadData

 MOVEA.LdCtlStorage(A1),A6;Get our global handle
 MOVEA.LioBuffer(A0),A4   ;Get the address of the “data”
 MOVE.W (A4),D0  ;Get the routine identifier
 ADD.W  D0,D0    ;Multiply it by two
 MOVE.W RoutineTable(D0),D0 ;Get routine offset
 JSR  RoutineTable(D0)  ;Go to the proper routine
 MOVE.L JIODone,-(A7);wrap up
 RTS
RoutineTable
 DC.W 0 ;1-based function codes...
 DC.W DriverAddDependency-RoutineTable
 DC.W DriverCompleteDependency-RoutineTable
 DC.W DriverRemoveDependency-RoutineTable
 DC.W DriverAvailableDependency-RoutineTable
 DC.W DriverStatus-RoutineTable
 DC.W DriverCensus-RoutineTable
 DC.W DriverWriteData-RoutineTable
 DC.W DriverReadData-RoutineTable

 ENDPROC

;
; This routine adds the source of a link to the dependency table
;
DriverAddDependencyPROC
 IMPORT FindSlotID

AddDepRec RECORD 0
Func    DS.W1
docID   DS.L1
slot_ID DS.W1
hatCheckDS.W1
edition DS.W1
AddDepRecSize  EQU *
 ENDR

 MOVEA.L(A6),A2  ;Get globals pointer
 LEA  IACGlobals.DocIDs(A2),A3;Point past end of               
 extentTable
 LEA  IACGlobals.ExtentTable(A2),A2;Point to base of           
 extent table
 MOVEQ  #0,D2    ;Clear our counter
AddDepLoop
 TST.L  ExtentRec.docID(A2) ;Is this slot available?
 BEQ.S  FoundAvailSlot    ;If so, we’re done looking
 ADD.L  #ExtentRec.ExtentSize,D2 ;Maintain “available”         
 offset
 ADDA.L #ExtentRec.ExtentSize,A2 ;Else point to next           
 record
 CMPA.L A2,A3    ;Are we done?
 BGT.S  AddDepLoop ;If not, look again
 
AddDepFail
 MOVE.W #NoMoreDocs,D0    ;Return custom OSErr
 BRA  AddDepExit ;And leave

FoundAvailSlot
 MOVE.L AddDepRec.docID(A4),D0;Get the docID
 BNE.S  ExistingDocID;Go if it already exists
 MOVE.L Time,D0  ;Else copy it from low RAM
 MOVE.L D0,AddDepRec.docID(A4);Update param block

ExistingDocID
 MOVE.L D0,ExtentRec.docID(A2);Store the docID
 MOVE.L D0,D5;   ;Save for later
 MOVE.L A2,D4    ;Save extent-rec addr for                     
 later
 MOVE.W AddDepRec.slot_ID(A4),D1 ;Did we get passed a          
 slot_ID?
 BEQ.S  NeedASlot;Go get one if not
 MOVE.L (A6),A3
 LEA  IACGlobals.DocIDS(A3),A3;Pt to table of unique           
 IDs
 SUBQ.W #1,D1    ;Adjust index to 0-based
 LSL.W  #2,D1    ;Convert to offset
 CMPI.L #DummyID,0(A3,D1.W) ;Is passed slot_ID target          
 only?
 BEQ.S  HaveASlot;Yes, put in real docID
 CMP.L  0(A3,D1.W),D5;ID in table OK?
 BEQ.S  HaveASlot;Yes, use slotID as is
 NeedASlot
 BSR  FindSlotID ;Get a valid slot ID
 BLT.S  AddDepExit ;No slots - Leave
 HaveASlot
 MOVE.L D5,0(A3,D1.W);Save docID in slot
 MOVEA.L(A6),A2  ;Dereference global handle
 MOVE.W D0,AddDepRec.slot_ID(A4) ;Store the slot_ID
 MOVE.W AddDepRec.hatCheck(A4),D1  ;Is there already a         
 hatCheck?
 BNE.S  StuffHatCheckCalc ;If so, bypass calculation 
 LEA  IACGlobals.docIDs(A2),A3;Point past the end
 LEA  IACGlobals.ExtentTable(A2),A2;Point to right             
 place

; Search for highest hatCheck registered for this docID
 MOVEQ  #0,D1    ;Initialize counter
 MOVE.L AddDepRec.docID(A4),D0;Get the docID again

HatCheckLoop
 CMP.L  ExtentRec.docID(A2),D0;Is the docID the same?
 BNE.S  NotSame  ;No, ignore
 CMP.W  ExtentRec.hatCheck(A2),D1  ;Compare hatChecks
 BGE.S  NotSame  ;Go if no need for update
 MOVE.W ExtentRec.hatCheck(A2),D1  ;Save max hatCheck
 NotSame
 ADDA.L #ExtentRec.ExtentSize,A2 ;Point to next record
 CMPA.L A2,A3    ;Are we done?
 BGT.S  HatCheckLoop ;Go if not
 ADDQ.W #1,D1    ;Bump to next hatCheck
 MOVE.W D1,AddDepRec.hatCheck(A4)  ;Update param block

StuffHatCheckCalc
 MOVEA.LD4,A2    ;Point to “working” extent
 MOVE.W D1,ExtentRec.hatCheck(A2)  ;Store hatCheck
 MOVE.W #0,AddDepRec.edition(A4) ;Pass back edition zero
 MOVE.W #0,ExtentRec.edition(A2) ;Set edition zero
 MOVEA.L(A6),A2  ;Get globals pointer
 MOVE.L D2,IACGlobals.AvailDependency(A2)    ;It’s also the    
 available dependency
 ADD.W  #1,IACGlobals.ExtentCount(A2)
 MOVE.W #noErr,D0;We succeeded!
AddDepExit
 RTS

 ENDPROC

;
; This routine adds the destination to a link that’s been 
; started.  If a passed-in slot_ID conflicts with an existing 
;entry, it is re-assigned
DriverCompleteDependency  PROC
 IMPORT FindSlotID

CompDepRecRECORD 0
Func    DS.W1
docID   DS.L1
slot_ID DS.W1
hatCheckDS.W1
CompDepRecSize EQU *
 ENDR

 MOVEA.L(A6),A2  ;Dereference globals handle
 MOVE.L CompDepRec.docID(A4),D0    ;Get the passed docID
 BNE.S  HaveDocID;Go if we were passed docID
 MOVE.L IACGlobals.AvailDependency(A2),D1    ;Get available    
 dependency
 LEA  IACGlobals.ExtentTable(A2),A2;Point to extent            
 records
 MOVE.L ExtentRec.docID(A2,D1.L),D0;Get the docID
 with   CompDepRec
 MOVE.L D0,docID(A4) ;Return to sender
HaveDocID
 MOVE.W slot_ID(A4),D1    ;Did we get passed a slot_ID?
 BNE.S  ExistingSlotID    ;Go get one if not
 MOVE.L #DummyID,D3;Use dummy ID in slot for now
 BRA.S  NeedSlotID ;Go find slot to put it in
ExistingSlotID
 MOVE.L (A6),A3
 LEA  IACGlobals.DocIDS(A3),A3;Pt to table of unique           
 IDs
 SUBQ.W #1,D1    ;Adjust index to 0-based
 LSL.W  #2,D1    ;Convert to offset
 CMPI.L #DummyID,0(A3,D1.W) ;Is passed slot_ID a dummy?
 BNE.S  CheckSlotID;No, see if it’s OK
 MOVE.L D0,0(A3,D1.W);Yes, replace with real thing
CheckSlotID
 CMP.L  0(A3,D1.W),D0;DocIDs match?
 BEQ.S  HaveSlotID
NeedSlotID
 BSR  FindSlotID ;Get a valid slot ID
 BLT.S  CompDepExit;failed...
HaveSlotID
 MOVE.W D0,slot_ID(A4)    ;Store slot_ID in caller’s           
 block
 MOVE.L docID(A4),D3
 endwith;CompDepRec
 MOVE.L D3,(A3,D1.W) ;Store docID
 MOVEA.L(A6),A2  ;Dereference globals handle
 MOVE.L IACGlobals.AvailDependency(A2),D2    ;Get available    
 dependency index
 LEA  IACGlobals.ExtentTable(A2),A2;Point to extent            
 records
 with   ExtentRec
 MOVE.L MstrInterestMsk(A2,D2.L),D3;Use register to            
 address all 32 bits
 BSET D0,D3 ;Set the interested bit
 MOVE.L D3,MstrInterestMsk(A2,D2.L);Back into table
 MOVE.L EditionList(A2,D2.L),D1    ;Get the “Edition List      
 handle”
 BGT.S  WalkEditions0;If it’s a handle, deal with it
 MOVE.W #NoSuchDep,D0;set error code
 BEQ.S  CompDepExit;If it’s zero, we do nothing
 MOVE.L #0,EditionList(A2,D2.L)    ;Otherwise give it no       
 respect
 BRA.S  CompDepExit
 endwith;ExtentRec

WalkEditions0
 MOVE.W ExtentRec.HatCheck(A2,D2.L),D3
 MOVE.W D3,CompDepRec.hatCheck(A4) ;Return to caller
WalkEditions
 MOVEA.LD1,A3    ;Copy the handle
 MOVEA.L(A3),A3  ;Dereference the handle
 with   EditionHdr
 MOVE.L InterestMask(A3),D3 ;So we can use 32-bit mode
 BSET D0,D3 ;Set the appropriate bit
 MOVE.L D3,InterestMask(A3)
 MOVE.L NextEd(A3),D1;Get handle to next handle
 endwith;EditionHdr
 BNE.S  WalkEditions ;If there is one, deal with it
 MOVE.W #noErr,D0;We succeeded!

CompDepExit
 RTS
 
 ENDPROC

;
; This routine severs a link and removes it from the tables
;
DriverRemoveDependency  PROC
 IMPORT FindExtentByDandHC,WipeExtentData

RemovDepRec RECORD 0
Func    DS.W1
docID   DS.L1
slotID  DS.W1
hatCheckDS.W1
RemovDepRecSize  EQU *
 ENDR

 MOVEA.L(A6),A2  ;Dereference globals handle
 MOVE.L RemovDepRec.docID(A4),D0 ;Get the passed docID
 MOVE.W RemovDepRec.hatCheck(A4),D1;Get the passed             
 hatCheck
 JSR  FindExtentByDandHC  ;Find the extent
 BNE.S  RemovDepExit ;Use FEBAHDC’s errcode and scram.
 
 MOVEA.L(A6),A2  ;Dereference globals handle
 LEA  IACGlobals.ExtentTable(A2),A2;Get table base
 MOVE.L ExtentRec.MstrInterestMsk(A2,D2.L),D3      ;Get        
 interest mask
 MOVE.W RemovDepRec.slotID(A4),D4  ;Get the passed slotID
 BCLR D4,D3 ;Declare this slot uninterested
 BNE.S  RemovTarget;If bit WAS set, target was                 
 doing the removing 
RemovSource ; Otherwise it’s the source.
 MOVEA.LExtentRec.EditionList(A2,D2.L),A0    ;Stuff list       
 header into A0
 JSR  WipeExtentData ;Kill the extent’s data
 LEA  IACGlobals.ExtentTable(A2),A2;Get table base
 with   ExtentRec
 MOVE.L #0,DocID(A2,D2.L) ;Clear DocID
 MOVE.L #0,HatCheck(A2,D2.L);Clear HatCheck/Edition
 MOVE.L #0,MstrInterestMsk(A2,D2.L);Clear                      
 MstrInterestMsk
 MOVE.L #0,EditionList(A2,D2.L)    ;Clear EditionList
 endwith;ExtentRec
 MOVE.W #noErr,D0;Set return code
 BRA.S  RemovDepCleanup
RemovTarget
 TST.L  D3;Does anybody care?
 BEQ.S  RemovSource;If no, then go to sleep
 MOVE.W #noErr,D0;Set return code
RemovDepCleanup
 MOVE.W RemovDepRec.slotID(A4),D4  ;Get the passed slotID
 SUBQ.W #1,D4
 LSL.W  #2,D4    ;Turn into offset
 MOVEA.L(A6),A2  ;Dereference globals handle
 with   IACGlobals
 SUBQ.W #1,ExtentCount(A2);One less extent in world
 LEA  DocIDs(A2),A2;Point to table of unique IDs
 MOVE.L #0,0(A2,D4.W);Clear doc’s slot
 endwith;IACGlobals
RemovDepExit
 RTS    ;Bye-bye

 ENDPROC

;
; This routine marks a link source as “available for
; completion”
;
DriverAvailableDependency PROC
 IMPORT FindExtentByDandHC

AvailDepRec RECORD 0
Func    DS.W1
docID   DS.L1
hatCheckDS.W1
AvailDepRecSize  EQU *
 ENDR

 MOVEA.L(A6),A2  ;Dereference globals handle
 MOVE.L AvailDepRec.docID(A4),D0 ;Get the passed docID
 MOVE.W AvailDepRec.hatCheck(A4),D1;Get the passed             
 hatCheck
 JSR  FindExtentByDandHC  ;Get dependancy if it                
 exists
 BNE.S  AvailDepExit ;If subr plotzed get out. 
 MOVEA.L(A6),A2
 MOVE.L D2,IACGlobals.AvailDependency(A2)    ;Otherwise, we    
 have a winner!
 MOVE.W #noErr,D0;Set return code
AvailDepExit
 RTS    ;Bye-bye

 ENDPROC

;
; This routine lets the client know what’s happening
;
DriverStatusPROC

StatusRec RECORD 0
Func    DS.W1
slotID  DS.W1
versID  DS.W1
docCountDS.W1
extentCount DS.W 1
StatusRecSize  EQU *
 ENDR

 MOVEA.L(A6),A2  ;Dereference globals handle
 MOVE.W StatusRec.slotID(A4),D0    ;Get slotID
 MOVE.W #100,StatusRec.versID(A4)  ;Report version as ‘100’
 LEA  IACGlobals.AvailDependency(A2),A3;Point past end
 LEA  IACGlobals.DocIDs(A2),A2;Point to extent table
 MOVEQ  #0,D3    ;Clear our counter
CountDoxLoop
 TST.L  0(A2)    ;Is the docID for real?
 BGE.S  CountDoxSkip ;If neg, then a modern date
 ADDQ #1,D3 ; so add 1 to the count
CountDoxSkip
 ADDA.L #4,A2    ;Else point to next record
 CMPA.L A2,A3    ;Are we done?
 BGT.S  CountDoxLoop ;If not, look again
 MOVE.W D3,StatusRec.docCount(A4)  ;Report number of docs
 
 MOVEA.L(A6),A2  ;Dereference globals handle
 LEA  IACGlobals.DocIDs(A2),A3;Point past end
 LEA  IACGlobals.ExtentTable(A2),A2;Point to extent            
 table
 MOVEQ  #0,D3    ;Clear our counter
CountExtLoop
 with   ExtentRec
 MOVE.L MstrInterestMsk(A2),D4;Fetch interest mask
 BTST.L D0,D4    ;Is this slot interested?
 BEQ.S  CountExtSkip ;If not, then get next extent
 MOVEA.LEditionList(A2),A0;Get EditionList HANDLE
 endwith;ExtentRec
FindExtLoop
 MOVE.L A0,D4    ;valid address?
 BEQ.S  CountExtSkip ;No more editions, get out
 MOVEA.L(A0),A0
 MOVE.L EditionHdr.InterestMask(A0),D4
 BTST.L D0,D4  ;Is this edition interested?
 BEQ.S  FindExtSkip;No, so check next extent
 ADDI.L #1,D3    ;Yes, add 1 and 
 BRA.S  CountExtSkip ; Get out
FindExtSkip
 MOVEA.LEditionHdr.NextEd(A0),A0 ;Move next EditionList        
 in
 BRA.S  FindExtLoop;And test back there 
CountExtSkip
 ADDA.L #ExtentRec.ExtentSize,A2 ;Else point to next           
 record
 CMPA.L A2,A3    ;Are we done?
 BGT.S  CountExtLoop ;If not, look again
 MOVE.W D3,StatusRec.extentCount(A4) ;Report number of         
 extents
StatusExit
 MOVE.W #0,D0    ;Indicate exit OK
 RTS
 
 ENDPROC

;
; This routine returns a count of active programs, links, etc. to the 
client
;
DriverCensusPROC

Census1RecRECORD 0
docID   DS.L1
edition DS.W1
hatCheckDS.W1
Census1RecSize EQU *
 ENDR

CensusRec RECORD 0
Func    DS.W1
extentCount DS.W 1
Census1 DS.BCensus1Rec
CensusRecSize  EQU *
 ENDR

 MOVEA.L(A6),A2  ;Dereference globals handle
 MOVE.L #CensusRec.Census1,D4 ;Offset to write for             
 output recs
 LEA  IACGlobals.DocIDs(A2),A3;Point past end
 LEA  IACGlobals.ExtentTable(A2),A2;Point to extent            
 table
 MOVEQ  #0,D3    ;Clear our counter
CensusLoop
 TST.L  ExtentRec.docID(A2) ;Check this extent’s docID
 BEQ.S  CensusSkip ;If wrong, skip to next
 with   ExtentRec
 MOVE.L docID(A2),Census1Rec.docID(A4,D4.L)  ;Move docID
 MOVE.L Edition(A2),Census1Rec.edition(A4,D4.L)    ;Move       
 edition
 MOVE.L HatCheck(A2),Census1Rec.hatCheck(A4,D4.L)  ;Move       
 hatCheck
 endwith;ExtentRec
 ADDI.L #Census1Rec.Census1RecSize,D4;Move to next output      
 rec
 ADDQ.W #1,D3
CensusSkip
 ADDA.L #ExtentRec.ExtentSize,A2 ;Else point to next           
 record
 CMPA.L A2,A3    ;Are we done?
 BGT.S  CensusLoop ;If not, look again
CensusExit
 MOVE.W D3,CensusRec.extentCount(A4) ;Report extent count
 MOVE.W #noErr,D0;Nothing is wrong
 RTS    ;Bye-bye

 ENDPROC

;
; This routine is called by the client to write data to the 
; driver
DriverWriteData  PROC
 IMPORT FindExtentByDandHC

WriteRecRECORD 0
Func    DS.W1
docID   DS.L1
hatCheckDS.W1
edition DS.W1
formatCount DS.W 1
theData DS.L1
WriteRecSizeEQU  *
 ENDR

FmtDataBlockRECORD 0
formatCodeDS.L 1
dataSizeDS.L1
myData  DS.L1
 ENDR

 MOVEA.L(A6),A2  ;Dereference globals handle
 MOVE.L WriteRec.docID(A4),D0 ;Get the passed docID
 MOVE.W WriteRec.hatCheck(A4),D1 ;Get the passed hatCheck
 JSR  FindExtentByDandHC  ;Get dependancy if it                
 exists
 BEQ.S  TableSetup ;Get going. Otherwise,
 RTS    ;subr plotzed leave w/o popping A1

TableSetup
 MOVEA.L(A6),A2  ;Dereference globals handle
 LEA  IACGlobals.ExtentTable(A2),A2;Get table base
 MOVE.W WriteRec.formatCount(A4),D5;Get the passed             
 formatCount
 EXT.L  D5
 ASL.L  #2,D5    ;Make it an offset
 MOVE.L A1,-(SP) ;preserve dCtlStorage Ptr
 MOVEA.LWriteRec.theData(A4),A1    ;Get the passed             
 theData
 MOVEA.LA1,A0
 _HLock ;Freeze the user data
 MOVEA.L(A1),A1  ; And put the pointer in A1
 
 MOVE.L #EditionHdr.EditionSize,D0 ;How much global space      
 do we want?
 ADD.L  D5,D0    ;Add format entries
 _NewHandle sys,clear;Try to allocate it (sys                  
 temporary)
 BNE  WriteDataFailed; Couldn’t
 MOVE.L A0,D7    ;preserve

 MOVEA.L(A6),A2  ;Dereference globals handle
 LEA  IACGlobals.ExtentTable(A2),A2;Get table base
 with   ExtentRec
 ADDQ.W #1,Edition(A2,D2.L) ;Bump edition #
 MOVEA.LEditionList(A2,D2.L),A3    ;Get handle to new          
 chain head
 MOVE.L A0,EditionList(A2,D2.L)    ;Point to new head of       
 chain
 MOVE.L MstrInterestMsk(A2,D2.L),D6;Need to copy into          
 editionHdr
 endwith;ExtentRec
 MOVEA.LD7,A0    ;Retrieve editionHdr handle
 MOVEA.L(A0),A0  ;Hook list after new block
 MOVE.L A3,EditionHdr.NextEd(A0) ;Set EditionList->next
 MOVE.L D6,EditionHdr.InterestMask(A0) ;Remember who’s         
 interested
 MOVE.W WriteRec.formatCount(A4),D6;Get the passed             
 formatCount
 MOVE.W D6,EditionHdr.NumFormats(A0) ;set format count
 MOVEA.LD7,A0
 _HLock ;Freeze new block
 MOVEA.L(A0),A3  ;Deref it 
OncePerFormat
 SUBI.W #4,D5    ;Knock down the format offset
 MOVE.L FmtDataBlock.dataSize(A1),D0 ;Get this block’s         
 size
 ADDI.L #4,D0    ;Add room for the fmtcode
 MOVE.L D0,D6    ;Set up count
 SUBQ.L #1,D6    ;Adjust for DBRA
 _NewHandle sys,clear;Try to allocate it
 BNE.S  WriteDataFailed   ; Couldn’t

 MOVE.L A0,D6    ;Save handle to IAC data copy
 MOVE.L A1,D7    ;Save ptr to user data
 MOVEA.L(A0),A0  ;Deref the IAC copy block
 with   FmtDataBlock
 MOVE.L dataSize(A1),D0   ;Get this block’s size
 MOVE.L formatCode(A1),(A0)+;Copy the format code
 MOVEA.LA0,A1    ;Dest pointer for BlockMove
 MOVEA.LD7,A0    ;Source pointer for BlockMove
 ADDA.L #8,A0    ;Skip hdr info
 _BlockMove
 MOVE.L D7,A1
 MOVE.L dataSize(A1),D0   ;Get this block’s size
 endwith;FmtDataBlock
 ADDQ.L #8,D0    ;Allow for header
 ADD.L  D0,A1    ;Reset A1 to next format block
 MOVE.L D6,A0    ;Get handle to IAC data copy
 MOVE.L A0,EditionHdr.EdInstances(A3,D5.L)   ;Record handle in EditionList
 TST.L  D5;Are we done?
 BNE.S  OncePerFormat;Do it again 
 MOVEA.LWriteRec.theData(A4),A0    ;Get the passed             
 theData
 _HUnlock ;And unlock this puppy too
 MOVE.L ExtentRec.Edition(A2,D2.L),D4
 MOVE.L D4,WriteRec.edition(A4)    ;Update the edition
 MOVE.W #noErr,D0;We’re copacetic at this                      
 point
WriteDataExit
 MOVEA.L(SP)+,A1 ;restore dCtlStorage
 RTS    ;Bye-bye
WriteDataFailed
 MOVE.W #WriteFailed,D0   ;So solly
 BRA.S  WriteDataExit

 ENDPROC

;
; This routine is called by client to read data from the driver
;
DriverReadData PROC
 IMPORT FindExtentByDandHC,IsFormatAvailable

ReadRec RECORD 0
Func    DS.W1
docID   DS.L1
slotID  DS.W1
hatCheckDS.W1
edition DS.W1
formatPrefDS.L 3
formatCodeDS.L 1
theData DS.L1
ReadRecSize EQU  *
 ENDR

 MOVEA.L(A6),A2  ;Dereference globals handle
 MOVE.L ReadRec.docID(A4),D0;Get the passed docID
 MOVE.W ReadRec.hatCheck(A4),D1    ;Get the passed hatCheck
 JSR  FindExtentByDandHC  ;Get dependancy if it exists
 BNE  ReadDataExit ;If subr plotzed get out. Otherwise,
 MOVEA.L(A6),A2  ;Dereference globals handle
 LEA  IACGlobals.ExtentTable(A2),A3;Get table base
 MOVE.W ExtentRec.Edition(A3,D2.L),D4;Fetch latest stored      
 edition
 CMPI.W #-1,D4   ;If edition not -1
 BNE.S  LinkStillValid    ; Link is OK
 MOVE.W #MissingLink,D0   ; Else link was invalidated
 BRA  ReadDataExit ;Go bye-bye
LinkStillValid
 CMP.W  ReadRec.edition(A4),D4;Has he seen my latest?
 BLE.S  FetchLatestEdition
 MOVE.W #NoNewerEdition,D0;Yes, he has 
 BRA  ReadDataExit
FetchLatestEdition
 MOVEA.LExtentRec.EditionList(A3,D2.L),A0    ;The handle for   
 latest ed’n
 MOVEA.L(A0),A3  ;A3 -> latest ed’n
 _HLock ;Freeze this puppy
 LEA  ReadRec.formatPref(A4),A2  ;Point to first format
 JSR  IsFormatAvailable   ;Is format 1 ok?
 BNE.S  RenderMe ;If so, render it 
 LEA  4(A2),A2   ;Point to second format
 TST.W  (A2);If no format 2 
 BEQ  ReadDataFailed
 JSR  IsFormatAvailable   ;Is format 2 ok?
 BNE.S  RenderMe ;If so, render it 
 LEA  4(A2),A2   ;Point to third format
 TST.W  (A2);If no format 3 
 BEQ  ReadDataFailed
 JSR  IsFormatAvailable   ;Is format 3 ok?
 BEQ  ReadDataFailed ;If not, bail out 
RenderMe
 MOVE.L (A2),ReadRec.formatCode(A4);Set format code
 MOVEA.L(A6),A2  ;Dereference globals handle
 LEA  IACGlobals.ExtentTable(A2),A2;Get table base
 MOVEA.LExtentRec.EditionList(A2,D2.L),A0    ;The handle for latest ed’n
 MOVE.L A0,-(SP) ;Save for later
 _HUnlock ;Dismiss it
 MOVE.L D7,A0    ;Put render handle in A0
 _GetHandleSize  ;How big are we?
 SUBQ.L #4,D0    ;Fudge size param for type code
 MOVE.L D0,D6    ;Save size param
 _HLock ;Freeze the EdInstance
 MOVEA.L(A0),A3  ;Deref it
 MOVEA.LReadRec.theData(A4),A0;Get output handle
 MOVE.L D6,D0    ;Get real size
 _SetHandleSize  ;Make enough room 
 MOVE.L A1,-(SP) ;Stack dCtlEntry
 MOVEA.L(A0),A1  ;Destination
 LEA  4(A3),A0   ;Source starts after formatCode 
 MOVE.L D6,D0    ;Reload real size
 _BlockMove
 MOVE.L (SP)+,A1 ;Retrieve dCtlEntry
 MOVE.L D7,A0    ;Put render handle in A0
 _HUnlock ;Let it go
;
; Now delete edition if no further interest
;
KillEditionLoop
 TST.L  (SP);Valid address?
 BEQ.S  ReadDataOK ;Exit if not..
 MOVEA.L(SP),A0  ;Reload handle to edition header
 MOVEA.L(A0),A3  ;A3 -> latest ed’n
 with   EditionHdr
 MOVE.L InterestMask(A3),D5 ;Get interest mask
 MOVE.W ReadRec.slotID(A4),D0 ;Get bit position
 BCLR.L D0,D5    ;Clear interest bit
 MOVE.L D5,InterestMask(A3) ;Save updated mask
 BNE.S  ReadDataOK ;Still interested parties, keep data
 MOVE.W NumFormats(A3),D7 ;Loop limit
 SUBQ.W #1,D7    ;Set up for DBRA
 LEA  EdInstances(A3),A2  ;Point to base of table
 endwith;EditionHdr
KillLoop
 MOVEA.L(A2),A0  ;Handle to data
 _DisposHandle   ;Kill it
 LEA  4(A2),A2   ;Next entry
 DBRA D7,KillLoop
 MOVE.L EditionHdr.NextEd(A3),D5 ;Save Link
 MOVEA.L(SP),A0  ;Reload header handle
 _DisposHandle   ;Kill it
 MOVE.L D5,(SP)  ;Replace handle with link
 BRA.S  KillEditionLoop   ;Try removing earlier edition

ReadDataOK
 MOVEA.L(A6),A2  ;Dereference globals handle
 LEA  IACGlobals.ExtentTable(A2),A2;Get table base
 MOVE.L (SP)+,ExtentRec.EditionList(A2,D2.L) ;Relink handle for latest 
ed’n
 MOVE.W #0,D0    ;Indicate exit OK
ReadDataExit
 RTS

ReadDataFailed
 MOVE.W #ReadFailed,D0    ;So solly
 BRA.S  ReadDataExit

 ENDPROC

;
; Unused entry points that must be defined for the header’s jump table
;
DriverCtl PROC
 EXPORT DriverDone
DriverDone
 MOVEQ  #0,D0    ;Everything’s cool
 RTS    ;Return to sender

 ENDPROC

;
;Many years later...the Subroutines!!
;
;Returns D0.W = slot_ID
; A3,D1.W pointing to available entry in docID list
FindSlotIDPROC
 MOVEA.L(A6),A2  ;Dereference global handle
 LEA  IACGlobals.docIDs(A2),A3;Point to list of docIDs
 MOVEQ  #-4,D1   ;Starting index of zero
 MOVEQ  #0,D3    ;Starting slot_ID
SlotIDLoop
 ADD.W  #4,D1    ;Bump to next index
 ADD.W  #1,D3    ;Bump counter
 CMP.W  #NumOfDocs+1,D3   ;Are we done yet?
 BEQ.S  SlotIDFail ;If so, we can’t create slot_ID
 CMP.L  (A3,D1.W),D0 ;Compare it to our docID
 BEQ.S  StuffSlotID;Exit if we’re done
 TST.L  (A3,D1.W);See if open slot
 BNE.S  SlotIDLoop ;If not, try again
StuffSlotID
 MOVE.W D3,D0    ;Move to result register
SlotExit
 TST.W  D0
 RTS    ;Return to sender
SlotIDFail
 MOVE.W #NoMoreSlots,D0   ;Custom OSErr
 BRA.S  SlotExit

 ENDPROC

;
;Looks for dep in extent table by matching docID and hatCheck
;ASSUMES THE FOLLOWING
;D0 = docID
;D1 = hatCheck
;A6 = globals handle [always]
;RETURNS
;D0 = noErr OR NoSuchDep
;D2 = desired offset
;We merrily destroy A2 and A3 during this routine
;
FindExtentByDandHC PROC
 MOVEA.L(A6),A2
 LEA  IACGlobals.DocIDs(A2),A3;Point past end
 LEA  IACGlobals.ExtentTable(A2),A2;Point to extent table
 MOVEQ  #0,D2    ;Clear our offset
FEBDAHCLoop
 CMP.L  ExtentRec.docID(A2),D0;Check this extent’s docID
 BNE.S  SkipToNextExtent  ;If wrong, skip to next
 CMP.W  ExtentRec.hatCheck(A2),D1  ;Check this extent’s docID
 BNE.S  SkipToNextExtent  ;If wrong, skip to next
 MOVE.W #noErr,D0;Nothing is wrong
 BRA.S  FEBDAHCExit;Get out.
 
SkipToNextExtent
 ADD.L  #ExtentRec.ExtentSize,D2 ;Bump offset
 ADDA.L #ExtentRec.ExtentSize,A2 ;Point to next record
 CMPA.L A2,A3    ;Are we done?
 BGT.S  FEBDAHCLoop;If not, look again

FEBDAHCFail
 MOVE.W #NoSuchDep,D0;Return custom OSErr

FEBDAHCExit
 TST.W  D0
 RTS    ;Bye-bye

 ENDPROC

;
;This routine wipes out all data and formats thereof for a single extent
;ASSUMES
;A0 = current EditionList
;We merrily destroy A3,D0,and D5 (A2 restored from A6)
;RETURNS
;Nothing in particular
WipeExtentData PROC
 with IACGlobals
 with ExtentRec

 MOVE.L A0,D0    ;Test handle to Edition List
 BEQ.S  DoneExtent ;If none, do nothing
 MOVEA.LD0,A3    ;Keep Edition List handle

NextEdition
 MOVEA.LA3,A0    ;Get Edition List handle
 _HLock ;Lock the Edition List handle
 MOVEA.L(A3),A0  ;Get pointer to Edition List

 with EditionHdr
 LEA  EdInstances(A0),A2  ;Point to instance data
 MOVE.W NumFormats(A0),D5 ;Get counter of formats

KillFormat
 MOVEA.L(A2),A0  ;Get the handle to the data
 _DisposHandle   ;Get rid of it
 LEA  4(A2),A2   ;Point to next instance
 SUBQ.W #1,D5    ;Decrement the counter
 BNE.S  KillFormat ;And do it again

 MOVEA.L(A3),A0  ;Dereference the Edition List handle
 MOVE.L NextEd(A0),D5;Get handle to next Edition
 MOVEA.LA3,A0    ;Restore handle
 _HUnlock ;Unlock it
 _DisposHandle   ;Get rid of it
 TST.L  D5;Is there a next Edition?
 BEQ.S  DoneExtent ;If not, go to next extent
 MOVEA.LD5,A3    ;Otherwise move the handle
 BRA.S  NextEdition;And loop

 endwith;EditionHdr
 endwith;ExtentRec
 endwith;IACGlobals

DoneExtent
 MOVEA.L(A6),A2  ;Restore
 RTS

 ENDPROC

;
;Looks for an dep in the extent table by matching the docID and hatCheck
;ASSUMES THE FOLLOWING
;A2 = pointer to format(integer)
;A3 = pointer to Edition record
;RETURNS
;D7 = NIL OR handle to EdInstance
;We do not touch A2,A4 during this routine
;We merrily destroy A0,D4,D5 during this routine
;
IsFormatAvailablePROC
 MOVE.W EditionHdr.NumFormats(A3),D5 ;Get number of            
 formats available
 MOVE.L (A2),D4  ;Hold desired fmt code
 SUBI.W #1,D5
 LSL.W  #2,D5    ;Make it an offset
 MOVE.L #0,D7    ;Set return to NIL
FormatCheck
 BLT.S  FormatCheckDone   ;D5 is now zero
 MOVEA.LEditionHdr.EdInstances(A3,D5.W),A0   ;Move handle to EdInstance
 MOVE.L A0,D7    ;Save in case it’s good
 MOVEA.L(A0),A0  ;Deref
 CMP.L  (A0),D4  ;Are the formats the same??
 BEQ.S  FormatCheckDone   ;If so, leave
 SUBI.W #4,D5    ;Check next one back 
 BRA.S  FormatCheck
FormatCheckDone
 TST.L  D7;Set the condition code first
 RTS

 ENDPROC

 END

{6}
Listing SAWSINIT.a

;**************************************************************
;*****The SAWS Inter-Application Communication Driver Loader       
;*****Written with blazing speed 6-7/88 by Paul F. Snively        
;*****  With one HELL of a lotta help from Frank Alviani          
;**************************************************************
;
;Modification History:
;6/19/88 First Draft--Paul F. Snively
;7/10/88 Hopefully last draft--this SHOULD work--
;Paul F. Snively
;8/21/88 Now searches for open slot from end of table--
;Frank Alviani
;(Algorithm from Pete Helme, Apple MACDTS)
;
;Basically what this puppy does is to assume that there’s a 
;DRVR resource lying around that happens to be named “.IAC”
;and, if there is, it loads it into the
;System Heap and opens it.  Simple, huh?
;

 INCLUDE‘Traps.a’
 INCLUDE‘QuickEqu.a’
 INCLUDE‘SysEqu.a’
 INCLUDE‘ToolEqu.a’
;STRING ASIS

successID EQU  128
failureID EQU  129

SAWSINIT: PROC

 IMPORT ShowINIT

Frame RECORD4,DECR
return  DS.L1
A6Link  DS.L1
theID DS.W1
theType DS.L1
name  DS.B256
fSize EQU *
 ENDR

 LINK A6,#Frame.fSize;space for locals...
; Find an open slot in the driver table and load into that
 MOVE.W UnitNtryCnt,D2    ;How many slots?
 SUBQ.W #1,D2    ;Adjust
 MOVE.W D2,D1    ;Set up offset
 LSL.W  #2,D1
 MOVEA.LUTableBase,A0;Where’s the table?
SrchLoop
 TST.L  0(A0,D1.W) ;Available?
 BEQ.S  GotSlot  ;Yup...
 SUBQ.L #4,D1    ;Drop Offset
 SUBQ.W #1,D2    ;Drop slot ID
 CMPI.L #39,D2   ;At bottom limit?
 BGT.S  SrchLoop
 BRA.S  BadNews  ;No open slots in the inn
;Get resource by name
GotSlot
 SUBQ.W #4,A7    ;Space for handle
 MOVE.L #$44525652,-(A7)  ;’DRVR’
 PEA  DriverName
 _GetNamedResource
 MOVE.W ResErr,D0;Get it?
 BNE.S  BadNews
 MOVE.L (A7)+,D7 ;Was there enough memory?
 BEQ.S  BadNews
;Change ID to open slot
 MOVE.L D7,-(A7)
 PEA  Frame.theID(A6);ID
 PEA  Frame.theType(A6)   ;theType
 PEA  Frame.name(A6) ;name
 _GetResInfo
 MOVE.L D7,-(A7)
 MOVE.W D2,-(A7)
 PEA  Frame.name(A6) ;name
 _SetResInfo
;Open the driver!
 CLR.W  -(A7)    ;Room for refnum
 PEA  DriverName ;Point to driver name
 _OpenDeskAcc    ;Open it
 MOVE.W (A7)+,D1 ;Pop refnum
;Ensure Driver undisturbed
 MOVE.L D7,-(A7)
 _DetachResource
;Restore previous slot in file
 MOVE.L #$44525652,-(A7)  ;’DRVR’
 PEA  DriverName
 _GetNamedResource
 MOVE.L D7,-(A7)
 MOVE.W Frame.theID(A6),D0
 MOVE.W D0,-(A7)
 MOVE.L #0,-(A7) ;leave alone name
 _SetResInfo
 MOVE.W #successID,-(SP)  ;Stack success ICN# ID
ShowICON:
 MOVE.W #-1,-(SP);Use standard pixel offset
 MOVE.L (SP)+,D0 ;TEMPORARY! POP OFF PARMS
;JSR  ShowINIT ;Tell user that driver installed
 UNLK A6
 RTS    ;And that’s all, folks!
BadNews:
 MOVE.W #failureID,-(SP)  ;Tell user we failed                 
 miserably
 BRA.S  ShowICON ;And leave

DriverName:
 DC.B ‘.IAC’;The name of our driver

 ENDPROC
 
 END
{7}
Listing SAWSINIT.r

/* The Resource Description File for the IAC Driver
 Frank Alviani -- 8/88
*/

include $$Shell(“hlxEtc”) “SAWSdrvr.lnk” ‘DRVR’ (31)
 as ‘DRVR’ (31,”.IAC”,sysheap,nonpurgeable); /* get DRVR resource */
include $$Shell(“hlxEtc”) “SAWSINIT”;/* get INIT resource */

/* ICN# s will go here asap */
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Marvel Future Fight celebrates nine year...
Announced alongside an advertising image I can only assume was aimed squarely at myself with the prominent Deadpool and Odin featured on it, Netmarble has revealed their celebrations for the 9th anniversary of Marvel Future Fight. The Countdown... | Read more »
HoYoFair 2024 prepares to showcase over...
To say Genshin Impact took the world by storm when it was released would be an understatement. However, I think the most surprising part of the launch was just how much further it went than gaming. There have been concerts, art shows, massive... | Read more »
Explore some of BBCs' most iconic s...
Despite your personal opinion on the BBC at a managerial level, it is undeniable that it has overseen some fantastic British shows in the past, and now thanks to a partnership with Roblox, players will be able to interact with some of these... | Read more »
Play Together teams up with Sanrio to br...
I was quite surprised to learn that the massive social network game Play Together had never collaborated with the globally popular Sanrio IP, it seems like the perfect team. Well, this glaring omission has now been rectified, as that instantly... | Read more »
Dark and Darker Mobile gets a new teaser...
Bluehole Studio and KRAFTON have released a new teaser trailer for their upcoming loot extravaganza Dark and Darker Mobile. Alongside this look into the underside of treasure hunting, we have received a few pieces of information about gameplay... | Read more »
DOFUS Touch relaunches on the global sta...
After being a big part of a lot of gamers - or at the very least my - school years with Dofus and Wakfu, Ankama sort of shied away from the global stage a bit before staging a big comeback with Waven last year. Now, the France-based developers are... | Read more »

Price Scanner via MacPrices.net

Sunday Sale: 13-inch M3 MacBook Air for $999,...
Several Apple retailers have the new 13″ MacBook Air with an M3 CPU in stock and on sale today for only $999 in Midnight. These are the lowest prices currently available for new 13″ M3 MacBook Airs... Read more
Multiple Apple retailers are offering 13-inch...
Several Apple retailers have 13″ MacBook Airs with M2 CPUs in stock and on sale this weekend starting at only $849 in Space Gray, Silver, Starlight, and Midnight colors. These are the lowest prices... Read more
Roundup of Verizon’s April Apple iPhone Promo...
Verizon is offering a number of iPhone deals for the month of April. Switch, and open a new of service, and you can qualify for a free iPhone 15 or heavy monthly discounts on other models: – 128GB... Read more
B&H has 16-inch MacBook Pros on sale for...
Apple 16″ MacBook Pros with M3 Pro and M3 Max CPUs are in stock and on sale today for $200-$300 off MSRP at B&H Photo. Their prices are among the lowest currently available for these models. B... Read more
Updated Mac Desktop Price Trackers
Our Apple award-winning Mac desktop price trackers are the best place to look for the lowest prices and latest sales on all the latest computers. Scan our price trackers for the latest information on... Read more
9th-generation iPads on sale for $80 off MSRP...
Best Buy has Apple’s 9th generation 10.2″ WiFi iPads on sale for $80 off MSRP on their online store for a limited time. Prices start at only $249. Sale prices for online orders only, in-store prices... Read more
15-inch M3 MacBook Airs on sale for $100 off...
Best Buy has Apple 15″ MacBook Airs with M3 CPUs on sale for $100 off MSRP on their online store. Prices valid for online orders only, in-store prices may vary. Order online and choose free shipping... Read more
24-inch M3 iMacs now on sale for $150 off MSR...
Amazon is now offering a $150 discount on Apple’s new M3-powered 24″ iMacs. Prices start at $1149 for models with 8GB of RAM and 256GB of storage: – 24″ M3 iMac/8-core GPU/8GB/256GB: $1149.99, $150... Read more
15-inch M3 MacBook Airs now on sale for $150...
Amazon is now offering a $150 discount on Apple’s new M3-powered 15″ MacBook Airs. Prices start at $1149 for models with 8GB of RAM and 256GB of storage: – 15″ M3 MacBook Air/8GB/256GB: $1149.99, $... Read more
The latest Apple Education discounts on MacBo...
If you’re a student, teacher, or staff member at any educational institution, you can use your .edu email address when ordering at Apple Education to take up to $300 off the purchase of a new MacBook... Read more

Jobs Board

Early Preschool Teacher - Glenda Drive/ *Appl...
Early Preschool Teacher - Glenda Drive/ Apple ValleyTeacher Share by Email Share on LinkedIn Share on Twitter Read more
Retail Assistant Manager- *Apple* Blossom Ma...
Retail Assistant Manager- APPLE BLOSSOM MALL Brand: Bath & Body Works Location: Winchester, VA, US Location Type: On-site Job ID: 04225 Job Area: Store: Management Read more
Housekeeper, *Apple* Valley Village - Cassi...
Apple Valley Village Health Care Center, a senior care campus, is hiring a Part-Time Housekeeper to join our team! We will train you for this position! In this role, Read more
Sonographer - *Apple* Hill Imaging Center -...
Sonographer - Apple Hill Imaging Center - Evenings Location: York Hospital, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now See Read more
Senior Software Engineer - *Apple* Fundamen...
…center of Microsoft's efforts to empower our users to do more. The Apple Fundamentals team focused on defining and improving the end-to-end developer experience in Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.