Multiprocessing With Smalltalk/V

Find out what's in store for Ken as he adds multiprocessing capabilities to Smalltalk/V, using the CX Multiprocessing Kit to build a simulated supermarket.


May 01, 1990
URL:http://www.drdobbs.com/embedded-systems/multiprocessing-with-smalltalkv/184408350

Figure 1

Figure 1

Figure 2

Figure 3

Figure 4

MAY90: MULTIPROCESSING WITH SMALLTALK/V

MULTIPROCESSING WITH SMALLTALK/V

A look at the CX Multiprocessing Extension Kit

This article contains the following executables: AYERS.ZIP

Kenneth E. Ayers

Ken is a software engineer currently employed by Industrial Data Technologies in Westerville, Ohio, where he is involved in the design of industrial graphic workstations. He also works part time as a consultant, specializing in prototyping custom software systems and applications. He can be contacted at 7825 Larchwood Street, Dublin, OH 43017.


The origins of object-oriented programming in general and Smalltalk in particular are closely tied to the simulation of real-world events and systems. A primary reason for this association is Smalltalk's facility for describing the behavior of a simulated system in terms of those real objects with which the implementor is familiar. Thus, the programmer of a highway traffic simulation can create car objects and truck objects and give them behaviors that mimic their real-world counterparts. Likewise, someone who is studying the flow of people through supermarket checkout lines can create objects representing customers, checkers and baggers and have them carry out actions that are familiar to anyone who has spent time waiting behind a shopping cart.

As an added benefit, extensible systems such as Smalltalk permit the developer of a simulation system to construct his or her own task language with which to describe the flow of information back and forth between the various participants in the simulation. This task language then becomes a part of the development system.

Until recently, though, the availability of object-oriented languages such as Smalltalk were limited to those using powerful (and expensive) workstations or minicomputers. Fortunately, that changed when Digitalk (Los Angeles, Calif.) introduced Smalltalk/V and its more powerful brother, Smalltalk/V 286. And, with a growing community of devoted followers, third-party support has finally begun to emerge. Because Smalltalk is such an open-ended system, this support commonly assumes the form of toolkits that extend the capabilities of Smalltalk's built-in environment. One such toolkit is the CX Multiprocessing Extension Kit from Computas Expert Systems.

The Goods

The CX Multiprocessing Extension Kit (the Kit) provides many useful extensions to the Smalltalk/V or Smalltalk/V 286 environments. Within its 15 separate modules, the Kit provides extended functionality ranging from basic utility methods all the way up to a complete data acquisition class hierarchy. As is expected in the Smalltalk world, source code is provided for all of the classes and methods; and filein's (scripts to read source code into the Smalltalk system) are available for installing each of the modules. Briefly, the modules include:

The Goodies

Several of the extensions provided by the CX Multiprocessing Extension Kit depend upon capabilities added by one or more of the Goodies disks available (as options) from Digitalk. These dependencies, as well as the dependencies within the Kit's internal modules, are clearly specified in the documentation.

Of primary concern, however, is the lack of support for multiprocessing in the original Smalltalk/V. Users of Smalltalk/V must install the Goodies #l package before any of the CX multiprocessing extensions can be used. Users of Smalltalk/V 286 do not have this concern.

Otherwise, the Data Acquisition Module depends upon the Digitalk Communication Kit (for serial communications); the Freehand Drawing extensions depend upon Goodies #1 (to load and store images); the Text Editor extensions require Goodies #2; and the Browsner and Application Browsner modules need both Goodies #2 and Goodies #3.

One further note must be offered because it presents a problem when attempting to load the CX Multi-processing Kit. Smalltalk/V and Smalltalk/V 286 do not support floating point numbers without a math coprocessor present in the system. The CX Data Acquisition module is the only module that appears to require floating point capabilities. In order to load any other modules from the kit on a system that does not have an 80x87, the methods containsFloat and asFloat, in the Basic Extensions module (CXBSCEXT.PRJ), must be commented out. (Note: The Goodies #2 kit provides software floating point support.)

The Package

The evaluation copy I received was marked Version 1.0. It was supplied on a single 3.5-inch (720K format) diskette (a 5.25-inch format is also available). The data necessary to build the installation package has been compressed into several special .EXE files. One of two batch files (one for Smalltalk/V and one for Smalltalk/V 286) are invoked to initiate the creation of more than 60 source code and example files.

A concise, well written, 105-page manual describes each of the modules and provides simple installation instructions in the required sequence. The number of examples given in the manual is somewhat limited, although the Smalltalk/V source code for many others is available in various files. Consistent with Smalltalk/V's tutorial, these examples can be selected and executed using the File Browser.

Multiprocessing?

The Smalltalk/V 286 Virtual Machine (its kernel) contains support for scheduling the execution of multiple processes with consideration for priority levels. In the context of Smalltalk, a process is a block of code that is capable of executing as an independent program. During its lifetime, a process can assume any one of several states: Active, the process is currently executing and "owns" the computer; Ready, the process is ready to run but is waiting to be scheduled for execution; Blocked, the process is waiting for some resource to become available or for some event to occur (for example, a signal from another process); and Dead, the code associated with the process has reached its logical point of termination.

A process is created by sending the message fork or forkAt:aPriority to a block. An example is [self run] forkAt:2. In response, the Smalltalk/V kernel will create a Process object, whose code is the expression in the block ('self run'), and schedule it for execution at the next available opportunity. That opportunity comes when:

    1. The current active process terminates, becomes blocked or voluntarily relinquishes control by executing the statement Processor yield.

    2. There are no other ready processes at a higher-priority level.

    3. There are no other ready processes at the same priority level that have been ready for a longer period of time.

Note that Smalltalk's scheduling is non-preemptive or cooperative. There is no time slicer that periodically says "Okay process, you've had enough time. Time to let this other process have its chance." In Smalltalk, it's up to the programmer to assure that processes are "courteous" and periodically give others a shot at running the show.

By its very nature, multiprocessing capabilities are ideally suited to tasks such as industrial control and monitoring, and to a class of applications known as discrete event simulations. In both of these types of applications, multiprocessing allows each individual object participating in the application to be represented by separate processes. Processes communicate with one another by sending messages (via message queues) or by signaling (via semaphores) the occurrence of some event required for coordinating their activities.

Lining Up an Example Application

To demonstrate the use of multiprocessing and apply some of the many features offered by the CX Multiprocessing Kit, I have included a simple application that provides an animated simulation of checkout counters at a typical supermarket. In this application, there are five basic classes of objects and processes:

    1. Customer processes (instances of class Customer) enter a checkout line, wait to be served, empty their carts, and then leave.

    2. Checkout clerk processes (class Checker) accept items from the customer and hand them off to be put into grocery sacks.

    3. Bagger processes (class Bagger) accept items from the checkout clerk and place them into grocery sacks.

    4. Checkout counter processes (instances of class CheckoutCounter) are assigned one checkout clerk, one bagger, and a queue of customers who are waiting to be serviced.

    5. The "store" itself (represented by the class MarketSimulator), is a single process that generates new customers and collects information about the simulation as it runs. The classes Customer, Checker, Bagger, and CheckoutCounter are all subclasses of the class MarketActor. Listing One (page 114 lists the classes while Listing Two (page 118) lists the simulation program itself.

MarketActor is an abstract superclass that provides all of the common functionality such as displaying and animating, accessing size and position information, and starting and stopping the object's associated process. The sub-classes add additional behaviors that are more appropriate to the particular type of simulated actor.

Internally, the coordination between the checkout counter, the customer, the checker, and the bagger are carried out by passing command messages to other processes using instances of the class MessageQueue (which was added by the CX package). The typical sequence of events for this animated simulation is shown in Example 1.

Example 1: Typical sequence of events in the checkout counter simulation

  Customer (upon removing an item from the cart):
                self
                             animate; "Lean forward"
                             send:#takeItem to:checker.

  Checker (upon receiving 'takeItem' command):
                self
                             animate; "Turn to left";
                             send:#takeItem to:bagger;
                             send:#gotIt to:customer.

  Bagger (upon receiving 'takeItem' command):
                self
                             animate; "Turn to right"
                             send:#gotIt to:checker;
                             display.  "Turn back forward"

  Customer (upon receiving 'gotIt' command):
                self
                             display.  "Stand up straight"

  Checker (upon receiving 'gotIt' command from bagger)
                self
                             display; "Turn back forward again"
                             send:#removeItemFromCart to:customer.

  Customer (when the cart is empty):
                self
                             moveTo:counter exitPosition; "Move to exit"
                             send:#nextCustomer to:counter.

All of these interactions model quite closely (at least in my mind) the interactions between real people at a checkout counter. Note, however that much of the interaction I have included (that is, "take this item" and "got it!") is for the purpose of animating the simulation and serves no real purpose if the goal were merely to gather information about the simulated process. Figure 1 depicts a snapshot of this simulation while it is running.

Setting up

Setting up the sample simulation also makes use of some of the tools provided as part of the CX Multiprocessing Kit. In particular, the BitEditor is used to create a set of images used in the animation sequences. To do this, evaluate with doit, the Smalltalk expressions shown in Example 2.

Example 2: Smalltalk expressions to be evaluated with doit

  MarketImages := Dictionary new:7.
  MarketImages
                at:'PersonUp' put: (Form width:16 height:16).
  MarketImages
                at:'PersonRight' put: (Form width:16 height:16).
  MarketImages
                at:'PersonDown' put: (Form width:16 height:16).
  MarketImages
                at:'PersonLeft' put: (Form width:16 height:16).
  MarketImages
                at:'Customer' put: (Form width:16 height:32).
  MarketImages
                at:'CustomerReaching' put: (Form width:16 height:32).
  MarketImages
                at:'Counter' put: (Form width:32 height:64).

Then, for each of the keys (PersonUp, etc.) evaluate the expression BitEditor new openOn:(MarketImages at: '<key>'). Create the animation images using those in Figure 1, Figure 2, Figure 3, and Figure 4 as a guide. Because the BitEditor uses a fixed scale and can only handle bit maps up to about 60 pixels in height, the individual images should be magnified using the expression in Example 3. Finally, enter the simulation classes in the order shown in Figure 5.

Example 3: Smalltalk expression used to magnify image

  |oldImage newImage|
  MarketImages keysDo:[:key|
                oldImage := MarketImages at:key.
      newImage := oldImage magnify:oldImage boundingBox
                by:2@2.   MarketImages at:key put:newImage].

Figure 5: Hierarchy of classes for the market simulation

  RandomNumber
  EmptyMenu
  MarketActor
    Bagger
    Checker
    CheckoutCounter
    Customer
  MarketSimulator
  MarketWindow

Open the simulation window by evaluating the expression MarketWindow new open. The simulation is started by popping up the pane menu, START SIMULATION, and clicking on it.

Conclusions

The basic multiprocessing capabilities found in the Smalltalk/V 286 environment are sufficient, with some ingenuity, to create fairly elaborate simulations. The methods and classes included as part of the CX Multiprocessing Extension Kit offer many much needed enhancements to those limited capabilities. This is particularly true of the classes EventQueue and Message Queue, which facilitate the implementation of timed events and interprocess communications. (When I first implemented the example application accompanying this article, I did so without using the message queuing capabilities. The result was extremely cumbersome and prone to unexplained failures. The decision to pass command messages between processes made the flow of information explicit and made the entire application much more understandable.)

The package offers an extraordinarily large number of features, all of which appear to be quite useful to the serious Smalltalk/V developer. Furthermore, there seems to be little evidence of redundancy or overkill. Some of the features, particularly the browser extensions, are worthy of a review by themselves.

One of the few real faults I could find was the lack of concrete examples. As seems to be common practice, many of the examples, while certainly complete and accurate, are rather trivial. To someone having limited experience with Smalltalk's multiprocessing features, that type of example fails to illustrate adequately the key concepts involved in creating and coordinating multiple processes. Personally, I would prefer to see one or two really complete examples than a hundred ways to print "hello world" using multiple processes.

From an operation point of view, the use of named processes and semaphores tends to impair Smalltalk's garbage collection and tends to leave inaccessible objects lying around unclaimed. In all fairness, this anomaly is documented; yet its occurrence is still disconcerting. I noticed that the use of the Process Status Window also seemed to produce a similar effect.

It is noted that the use of timed events creates a high-priority background process to watch the clock and look for events that need to be triggered. This appeared to affect the performance of other parts of my simulation, slowing it down considerably. Thus, for example, to implement a "sleep for n seconds" method, I found that watching the time myself produced better results than scheduling an event for n seconds in the future. Undoubtedly, for infrequent events occurring at some absolute date/time, this feature would be invaluable; but for short, repetitive delay operations it seemed to incur a considerable overhead.

Finally, though the cost of the extension package itself is certainly reasonable, the hidden costs of the Goodies, required to make full use of its capabilities, nearly triples the upfront cost of using this package. Even though the Goodies packages do offer a lot of added functionality at a reasonable price, I feel it only fair to warn the potential user that they represent an additional cost.

The net result of my experience exploring the CX Multiprocessing Extension Kit was certainly positive. I have gained additional insight into the realm of multiprocessing within the context of Smalltalk and feel certain that potential users of this package would benefit from its diverse capabilities.

But for now, I'm going to get back to my simulated market and see if I can answer the question that has plagued modern man since the opening of the first supermarket: Why is the shortest line always the slowest?

Product Information

CX Multiprocessing Extension Kit Computas Expert Systems A.S (Veritasveien 1) P.O. Box 410 N-1322 Hovik, Norway Requirements: Smalltalk/V 2.0 or later Goodies disks #1, #2, and #3 Smalltalk/V 286, 1.1 or later recommended Price: $99.95

_MULTIPROCESSING WITH SMALLTALK/V_ by Kenneth Ayers

[LISTING ONE]



Object subclass: #RandomNumber
  instanceVariableNames: ''
  classVariableNames:
    'Seed '
  poolDictionaries: '' !

!RandomNumber class methods !
from:min to:max
    ^(self new \\ (max - min + 1)) + min.!
new
    |n|
    Seed isNil ifTrue:[self reset].
    n := Seed.
    Seed := (Seed * 263 + 30011) bitAnd:16r7FFF.
    ^n.!
randomize
    Seed := Time millisecondClockValue bitAnd:16r7FFF.!
reset
    Seed := 0.! !


Object subclass: #MarketActor
  instanceVariableNames:
    'running msgQueue position notify '
  classVariableNames: ''
  poolDictionaries: '' !

!MarketActor class methods !

extent
    ^self form extent.!
form
    ^self formNamed:self imageName.!
formNamed:anImageName
    arketImages at:anImageName.!
height
    ^self form height.!
imageName
    ^'PersonUp'.!
new
    ^super new initialize.!
priority
    ^2.!
width
    ^self form width.! !

!MarketActor methods !
alternateForm
    ^self formNamed:self class alternateImageName.!
animate
    self display:self alternateForm.!
display
    self display:self form at:position.!
display:aForm
    self display:aForm at:position.!
display:aForm at:aPoint
    aForm displayAt:aPoint.!
erase
    Display white:self frame.!
extent
    ^self class extent.!
form
    ^self class form.!
formNamed:anImageName
    ^self class formNamed:anImageName.!
frame
    ^position extent:self extent.!
height
    ^self class height.!
initialize
    msgQueue := MessageQueue new.
    running  := false.!
messageQueue
    ^msgQueue.!
moveBy:delta
    self moveTo:position + delta.!
moveTo:aPoint
    self
        erase;
        position:aPoint;
        display.!
notify
    (notify isKindOf:Semaphore)
        ifTrue:[notify signal].!
position:aPoint
    position := aPoint.!
priority
    ^self class priority.!
receive
    ^msgQueue receive.!
release
    msgQueue release.  msgQueue := nil.  notify := nil.
    super release.!
run
    |action|
    [running]
        whileTrue:[
            action := self receive.
            (running and:[self respondsTo:action])
                ifTrue:[self perform:action]].
    self shutdown.!
send:aMessage to:anObject
    self send:aMessage to:anObject with:nil.!
send:aMessage to:anObject with:anArgument
    |queue|
    (queue := anObject messageQueue) isNil
        ifFalse:[
            queue send:aMessage.
            anArgument isNil
            ifFalse:[queue send:anArgument]].
    Processor yield.!
shutdown
    self erase;  notify.!
sleep:numberOfSeconds
    |timeout|
    timeout := Time now asSeconds + numberOfSeconds.
    [running and:[Time now asSeconds < timeout]]
        whileTrue:[Processor yield].!
start
    running
        ifFalse:[
            running := true.
            [self run] forkAt:self priority].!
stop:notifySemaphore
    notify := notifySemaphore.
    running
        ifTrue:[
            running := false.
            self send:#wakeUp to:self]
        ifFalse:[self notify].!
wakeUp
        "In case the receiver is waiting
         at the message queue."!
width
    ^self class width.! !

MarketActor subclass: #Checker
  instanceVariableNames:
    'bagger customer '
  classVariableNames: ''
  poolDictionaries: '' !

!Checker class methods !
alternateImageName
    ^'PersonLeft'.! !

!Checker methods !
bagger:aBagger
    bagger := aBagger.!
checkOutCustomer
    customer := self receive.!
gotIt
    running
        ifTrue:[
            self
                display;
                send:#removeItemFromCart to:customer].!
release
    bagger := nil.  customer := nil.  super release.!
takeItem
    running
        ifTrue:[
            self
                animate;
                send:#takeItem to:bagger;
                sleep:1;
                send:#gotIt to:customer].! !


MarketActor subclass: #CheckoutCounter
  instanceVariableNames:
    'checker bagger customers nowServing '
  classVariableNames:
    'MaxCustomers '
  poolDictionaries: '' !

!CheckoutCounter class methods !
imageName
    ^'Counter'.!
initialize
    MaxCustomers isNil ifTrue:[MaxCustomers := 3].!
maxCustomers
    self initialize.
    axCustomers.!
maxCustomers:aNumber
    MaxCustomers := aNumber.!
new
    self initialize.
    ^super new.!
priority
    ^3.! !

!CheckoutCounter methods !
addCustomer
    |aCustomer|
    aCustomer := self receive.
    running
        ifTrue:[
            customers add:aCustomer.
            self length == 1
                ifTrue:[self nextCustomer]].!
checkoutPosition
        "Answer a Point, the checkout position."
    ^position - (Customer width @ 0).!
display
    super display.
    checker display.
    bagger display.!
endOfLinePosition
        "Answer a Point, the end of the checkout line."
    ^position
        - (Customer width
            @ (customers size + 1 * Customer height)).!
exitPosition
        "Answer a Point, the exit position."
    ^position + ((0 - Customer width) @ self height).!
initialize
    checker := Checker new.
    bagger  := Bagger new.
    checker bagger:bagger.
    bagger  checker:checker.
    customers := OrderedCollection new:MaxCustomers.
    super initialize.!
length
    ^customers size
        + (nowServing isNil ifTrue:[0] ifFalse:[1]).!
nextCustomer
    running
        ifTrue:[
            customers isEmpty
                ifTrue: [nowServing := nil]
                ifFalse:[
                    nowServing := customers removeFirst.
                    self send:#moveToCheckout
                            to:nowServing
                            with:checker.
                    customers do:[:aCustomer|
                        aCustomer isNil
                            ifFalse:[
                                self send:#moveForward to:aCustomer]]]].!
position:aPoint
    super position:aPoint.
    checker position:aPoint
                        + ((self width // 2)
                            @ (self height // 4)).
    bagger position:aPoint + (0 @ self height * 3 // 4).!
release
    bagger release.  checker    release.  nowServing release.
    bagger := nil.   checker := nil.      nowServing := nil.

    [customers isEmpty] whileFalse:[customers removeFirst release].
    customers release.  customers  := nil.  super release.!
shutdown
    |semaphore|
    self update:0.
    semaphore := Semaphore new.
    customers do:[:aCustomer|
            aCustomer stop:semaphore.
            semaphore wait].
    nowServing isNil
        ifFalse:[
            nowServing stop:semaphore.
            semaphore wait].
    checker isNil
        ifFalse:[
            checker stop:semaphore.
            semaphore wait].
    bagger isNil
        ifFalse:[
            bagger stop:semaphore.
            semaphore wait].
    super shutdown.!
start
    super start.
    bagger start.
    checker start.!
update
    |aValue|
    ((aValue := self receive) isKindOf:Number)
        ifTrue:[self update:aValue].!
update:aValue
    |fieldWidth textPosition extent|
    textPosition :=
        position  + ((self width // 2)
                        @ ((SysFont height + 2) negated)).
    fieldWidth := 3.
    extent := (SysFont width * fieldWidth)
                @ SysFont height.
    aValue == 0
        ifTrue: [Display white:(textPosition extent:extent)]
        ifFalse:[(aValue printString flushedRightIn:fieldWidth)
                        displayAt:textPosition].! !


MarketActor subclass: #Customer
  instanceVariableNames:
    'counter checker items leaving '
  classVariableNames:
    'MinItems MaxItems '
  poolDictionaries: '' !

!Customer class methods !
alternateImageName
    ^'CustomerReaching'.!
imageName
    ^'Customer'.!
new
    MinItems isNil ifTrue:[MinItems := 5.  MaxItems := 25].
    ^super new.! !

!Customer methods !
cartIsEmpty
    ^items <= 0.!
counter:aCheckoutCounter
    counter := aCheckoutCounter.!
gotIt
    self display;  sleep:1.!
initialize
    items := RandomNumber from:MinItems to:MaxItems.
    leaving := false.
    super initialize.!
leaveStore
    running
        ifTrue:[
            self
                update;
                moveTo:counter exitPosition;
                send:#nextCustomer to:counter.
            running := false.
            leaving := true].!
moveForward
    running ifTrue:[self moveBy:0 @ self height].!
moveToCheckout
    checker := self receive.
    running
        ifTrue:[
            self
                moveTo:counter checkoutPosition;
                send:#checkOutCustomer to:checker with:self;
                update.
            self removeItemFromCart].!
moveToEndOfLine
    running ifTrue:[self moveTo:counter endOfLinePosition].!
release
    checker := nil.  counter := nil.  super release.!
removeItemFromCart
    running
        ifTrue:[
            self cartIsEmpty
                ifTrue:[self leaveStore]
                ifFalse:[
                    self
                        update;
                        animate;
                        send:#takeItem to:checker.
                    items := items - 1]].!
shutdown
    super shutdown.
    leaving ifTrue:[self release].!
update
    (running and:[counter notNil])
        ifTrue:[self send:#update to:counter with:items].! !




[LISTING TWO]


Object subclass: #MarketSimulator
  instanceVariableNames:
    'running notify counters frame statusFrame totalCustomers startTime '
  classVariableNames:
    'MaxTime MaxCounters MinTime '
  poolDictionaries: '' !

!MarketSimulator class methods !
new
    MaxCounters := 3.  MinTime := 1.  MaxTime := 15.
    ^super new initialize.!
priority
    ^2.! !

!MarketSimulator methods !
elapsedTime
    |time field offset|
    time := Time now subtractTime:startTime.
    field := 'ELAPSED TIME ', time printString.
    offset := statusFrame center x
                - ((SysFont stringWidth:field) // 2).
    field displayAt:statusFrame origin + (offset @ 0).!
initialize
    totalCustomers := 0.
    running := false.!
newCustomer
    |counter customer|
    (counter := self shortestLine) isNil
        ifFalse:[
            customer := Customer new
                counter:counter;
                position:counter endOfLinePosition;
                display;
                start;
                yourself.
            totalCustomers := totalCustomers + 1.
            ('CUSTOMERS SERVED:', totalCustomers printString)
                displayAt:statusFrame origin.
            self send:#addCustomer to:counter with:customer].!
reframe:aFrame
    |statusHeight w h maxCounters maxCustomers x y|
    CursorManager execute change.
    frame := aFrame.
    statusHeight := SysFont height + 4.
    statusFrame := aFrame origin + (2 @ 2)
                        extent:(aFrame width - 4) @ statusHeight.
    w := CheckoutCounter width + Customer width + 2.
    h := CheckoutCounter height + Customer height + 4.
    maxCounters := ((aFrame width - Customer width) // w)
                        min:MaxCounters.
    maxCustomers := ((aFrame height - statusHeight - h)
                        // Customer height)
                            min:CheckoutCounter maxCustomers.
    x  := aFrame origin x
            + ((aFrame width - (maxCounters * w)) // 2)
            + Customer width.
    y  := aFrame corner y - h.
    CheckoutCounter maxCustomers:maxCustomers.
    counters := Array new:maxCounters.
    1 to:maxCounters do:[:i|
        counters
            at:i
            put:(CheckoutCounter new
                    position:x @ y;
                    display;
                    yourself).
        x := x + w].
    CursorManager normal change.!
release
    1 to:counters size do:[:i|
        (counters at:i) release.
        counters at:i put:nil].
    counters release.
    counters := nil.
    notify := nil.
    super release.!
run
    self newCustomer.
    [running]
        whileTrue:[

            self sleep:(RandomNumber from:MinTime to:MaxTime).
            running ifTrue:[self newCustomer]].
    self shutdown.
    (notify isKindOf:Semaphore)
        ifTrue:[notify signal].!
running
    ^running.!
send:aMessage to:anObject
    self send:aMessage to:anObject with:nil.!
send:aMessage to:anObject with:anArgument
    |queue|
    (queue := anObject messageQueue) isNil
        ifFalse:[
            queue send:aMessage.
            anArgument isNil ifFalse:[queue send:anArgument]].
    Processor yield.!
shortestLine
    |fewest length shortest|
    fewest := 9999.
    1 to:counters size do:[:i|
        (length := (counters at:i) length) < fewest
            ifTrue:[
                fewest := length.
                shortest := i]].
    fewest < CheckoutCounter maxCustomers
        ifTrue:[^counters at:shortest]
        ifFalse:[^nil].!
shutdown
    |semaphore|
    semaphore := Semaphore new.
    counters do:[:aCounter|
        aCounter isNil
            ifFalse:[
                aCounter stop:semaphore.
                semaphore wait]].!
sleep:numberOfSeconds
    |timeout lastTime time|
    timeout := Time now asSeconds + numberOfSeconds.
    lastTime := 0.
    [running and:[(time := Time now asSeconds) < timeout]]
        whileTrue:[
            time = lastTime
                ifFalse:[
                    self
                        timeRemaining:(timeout - time);
                        elapsedTime.
                    lastTime := time].
            Processor yield].!
start
    counters do:[:aCounter|
        aCounter isNil ifFalse:[aCounter start]].
    running
        ifFalse:[
            startTime := Time now.
            running := true.
            [self run] forkAt:self class priority].!
stop:notifySemaphore
    running
        ifTrue:[
            notify := notifySemaphore.
            running := false]
        ifFalse:[
            self shutdown.
            notifySemaphore signal].!
timeRemaining:seconds
    |fieldWidth field offset|
    fieldWidth := 3.
    field := 'NEXT CUSTOMER:',
                (seconds printString flushedRightIn:fieldWidth).
    offset := statusFrame width
                - (SysFont stringWidth:field).
    field displayAt:statusFrame origin + (offset @ 0).! !


Object subclass: #MarketWindow
  instanceVariableNames:
    'topPane aPane simulator '
  classVariableNames:
    'Version Frame Title '
  poolDictionaries: '' !

!MarketWindow class methods !
initialize
    Frame   := Display boundingBox insetBy:16.
    Title   := 'Market Checkout Simulation'.
    Version := ' (Version 1.0 -- 02/24/90 -- KEA)'.!
new
    self initialize.
    ^super new.! !

!MarketWindow methods !
close
    self stop.!
initPane:aFrame
    Display white:aFrame.
    self initSimulator:aFrame.
    ^Form fromDisplay:aFrame.!
initSimulator:aFrame
    simulator isNil
        ifTrue:[
            simulator := MarketSimulator new
                reframe:aFrame;
                yourself].!
open
    topPane := TopPane new
        model:self;
        label:Title, Version;
        menu:#windowMenu;
        rightIcons:#(collapse);
        yourself.
    topPane addSubpane:
        (aPane := GraphPane new
            model:self;
            name:#initPane:;
            menu:#paneMenu;
            framingRatio:(0 @ 0 extent:1 @ 1)).
    topPane reframe:Frame.
    topPane dispatcher openWindow scheduleWindow.!
paneMenu
    (simulator isNil or:[simulator running])
        ifTrue:[^EmptyMenu new]
        ifFalse:[
            enu
                labels:'START SIMULATION'
                lines:#()
                selectors:#(start)].!
start
    simulator start.!
stop
    |semaphore|
    simulator isNil
        ifFalse:[
            CursorManager execute change.
            semaphore := Semaphore new.
            simulator stop:semaphore.
            semaphore wait;  release.
            simulator release.  simulator := nil.
            CursorManager normal change].!
windowMenu
    enu
        labels:'cycle\collapse\close' withCrs
        lines:#()
        selectors:#(cycle collapse closeIt).! !



[Example 1: Typical sequence of events in the checkout counter-simulation]

   Customer
 (upon removing an item from the cart):
      self
         animate;  "Lean forward"
         send:#takeItem to:checker.

   Checker
 (upon receiving 'takeItem' command):
      self
         animate;  "Turn to left";
         send:#takeItem to:bagger;
         send:#gotIt to:customer.

   Bagger
 (upon receiving 'takeItem' command):
      self
         animate;  "Turn to right"
         send:#gotIt to:checker;
         display.  "Turn back forward"

   Customer
 (upon receiving 'gotIt' command):
      self
         display.  "Stand up straight"


   Checker
 (upon receiving 'gotIt' command from bagger)
      self
         display;  "Turn back forward again"
         send:#removeItemFromCart to:customer.

   Customer
 (when the cart is empty):
      self
         moveTo:counter exitPosition;  "Move to exit"
         send:#nextCustomer to:counter.

[Example 2: Smalltalk expressions to be evaulated with doit]

   MarketImages := Dictionary new:7.
   MarketImages
      at:'PersonUp' put:(Form width:16 height:16).
   MarketImages
      at:'PersonRight' put:(Form width:16 height:16).
   MarketImages
      at:'PersonDown' put:(Form width:16 height:16).
   MarketImages
      at:'PersonLeft' put:(Form width:16 height:16).
   MarketImages
      at:'Customer' put:(Form width:16 height:32).
   MarketImages
      at:'CustomerReaching' put:(Form width:16 height:32).
   MarketImages
      at:'Counter' put:(Form width:32 height:64).


[Example 3: Smalltalk expression used to magnify image]

   |oldImage newImage|
   MarketImages keysDo:[:key|
      oldImage := MarketImages at:key.
      newImage := oldImage magnify:oldImage boundingBox
                     by:2@2.
      MarketImages at:key put:newImage].


[Figure 5: Hierarchy of classes for the market simulation]

      RandomNumber
      EmptyMenu
      MarketActor
         Bagger
         Checker
         CheckoutCounter
         Customer
      MarketSimulator
      MarketWindow









Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.