ThinkC [Study Group 2] - Events & Menu Management

Relating to ThinkC Development

eric

Administrator
Staff member
Sep 2, 2021
822
1,307
93
MN
scsi.blue
If you missed Study Group 0 don't worry! Come back after you've setup your environment here https://tinkerdifferent.com/threads...velopment-environment-setup-hello-world.1754/

Remember if you're not ready to move on to this week, don't worry, just come back when you are, we'll still be here to help!

Our goals for this week:
  • Read Chapter 4 & 5 in Macintosh C Programming Primer - Events and Menu Management
    • Complete the examples EventTracker, ShowPICT, WorldClock
  • Familiarize yourself with the Event Manager in Think Reference.
  • Take what you've learned build these concepts into your existing apps or a new app!
  • Post here with questions, tips, and help each other out
Events and Menu's are essential to any Mac application and are a powerful concept, especially in the late 80's. Lots to learn, so let's get going!
 

eric

Administrator
Staff member
Sep 2, 2021
822
1,307
93
MN
scsi.blue
One thing I ran into recently with events is cancelling a process while processing an event. Everything is single threaded so no events are processed while your handling the current event. The way I handle this is to occasionally call the EventAvail(eventMask, theEvent) and just look if there is a keypress event I'm interested in (command .) and then bail out of the loop.
 
  • Like
Reactions: Mu0n

Kabootie Computey!

New Tinkerer
Sep 13, 2022
15
19
3
www.kabootie.com
Sweet! For Study Group 2 I'm going to try to make "Multi Pong": a game that's just like normal pong, except that the player's paddle is locked to a fixed spot in a window, so you need to move the windows themselves around in order to line up your shots. My first accomplishment today is abusing GlobalToLocal() and LocalToGlobal() to make a puck bounce around within the bounds of the screen, and then display whenever it's inside a game window. Next, I want to figure out a way to work around the fact that the puck stops moving while the player is dragging a window.

Apologies for the video quality... these old monitors, amirite?

 

Kabootie Computey!

New Tinkerer
Sep 13, 2022
15
19
3
www.kabootie.com
Wo0t! I got everything (mostly!) working together, and Multi Pong is now a playable game. See the source and download it from Github.

There are a few things I can't figure out yet - if anybody has clever ideas, let me know.
  1. The ball is flickery. I'm animating it with alternating calls to EraseRect() and DrawOval(), but that all happens slowly enough to see some real flicker. I solved this for the opponent's puck by only erasing the teeniest portion possible when it moves. [Edit: lol, look at that - offscreen buffers. I'll try that later.]
  2. Collision detection with SectRect() doesn't seem to detect overlapping rectangles for like one pixel of the right and bottom side of the puck, so when the puck hits the wall on those sides, it overlaps (and subsequently erases) a teeny bit of the wall. [Edit: worked around for now.]
  3. I needed to import ANSI-small to use sprintf() for the scoreboard text ("Player 1 | Opponent 1"), which seems... excessive. Is there some more idiomatic way to create a Str255 that includes dynamic values?
  4. I realized it's possible to cheat by dragging the big window and plopping the opponent's goal on top of the ball. I might need to make the big window unmovable. :p In the meantime, don't do that! Win with dignity! [Edit: fixed.]
  5. A couple of bugs I want to fix: the ball will sometimes erase some of the playfield. (I think I know everything I need to fix this one.) It will also bounce off surfaces you can't actually see, because the window with that surface is behind another one. (I'm not totally sure I know how to detect this condition.)
Enjoy!

screenshot.png
 
Last edited:

Crutch

Tinkerer
Jul 10, 2022
292
226
43
Chicago
This is such a cool idea, I am excited to try it out. Some possibly useful info:

3. Your smart linker should rip out everything in ANSI-small except sprintf() so this shouldn’t actually cost you much space. But yes, I would do it differently. Either create a dummy string (Str255 s = “\pPlayer X | Opponent X”) and poke the appropriate char where the X is (works as long as you max out the score at 7 :) ), or write your own 3-line Pascal string concatenator function with BlockMove, and use NumToString() to stringify the score.

5. The visible part of a window is contained in the visRgn field of its WindowRecord, e.g. ((WindowPeek) myWindowPtr)->visRgn. To check if an obstacle is totally covered by another window, turn the obstacle into a region (use RectRgn) then check to see if that region intersects with the window’s visRgn (use SectRgn). If the resulting intersection region is empty (use EmptyRgn), the obstacle is not visible. If the intersection region is smaller than the original obstacle region you got from RectRgn (use DiffRgn, then EmptyRgn to see if the difference is non-empty), the obstacle is partially but not totally obscured.

Finally - this is not actually useful info right now, since you are clearing not running on a compact Mac - but per your #1, on a compact Mac, another way to achieve simple flicker-free animation is to do all your drawing during a vertical blanking interrupt while the scanline is retracing to the top of the screen (and turned off). If you complete all your drawing in this interval, there will be no flicker. The easiest way to do this is to poll TickCount() and draw as soon as it changes (every 1/60 second).
 
  • Like
Reactions: Kabootie Computey!

Kabootie Computey!

New Tinkerer
Sep 13, 2022
15
19
3
www.kabootie.com
3. Your smart linker should rip out everything in ANSI-small except sprintf() so this shouldn’t actually cost you much space. But yes, I would do it differently. Either create a dummy string (Str255 s = “\pPlayer X | Opponent X”) and poke the appropriate char where the X is (works as long as you max out the score at 7 :) ), or write your own 3-line Pascal string concatenator function with BlockMove, and use NumToString() to stringify the score.

Man I spent like an hour looking for a Toolbox routine that I assumed must exist. It's a relief to confirm that I wasn't just bad at reading documentation! I'll indeed roll my own - thanks for those suggestions.

5. The visible part of a window is contained in the visRgn field of its WindowRecord, e.g. ((WindowPeek) myWindowPtr)->visRgn. To check if an obstacle is totally covered by another window, turn the obstacle into a region (use RectRgn) then check to see if that region intersects with the window’s visRgn (use SectRgn). If the resulting intersection region is empty (use EmptyRgn), the obstacle is not visible. If the intersection region is smaller than the original obstacle region you got from RectRgn (use DiffRgn, then EmptyRgn to see if the difference is non-empty), the obstacle is partially but not totally obscured.

Super clear - thanks!

Finally - this is not actually useful info right now, since you are clearing not running on a compact Mac - but per your #1, on a compact Mac, another way to achieve simple flicker-free animation is to do all your drawing during a vertical blanking interrupt while the scanline is retracing to the top of the screen (and turned off). If you complete all your drawing in this interval, there will be no flicker. The easiest way to do this is to poll TickCount() and draw as soon as it changes (every 1/60 second).

Those color graphics gave me away, huh? :D And actually I've been assuming - but have only anecdotally verified - that structuring the event loop like this will effectively lock the game logic to the tick counter, because the game loop updates during null events. But... is that a bad assumption?

C:
if ( WaitNextEvent( everyEvent, &event, 1L, nil ) ) {
    // handle key presses and mouse clicks and such
} else {
    GameLoop();
}
 
Last edited:
  • Like
Reactions: Crutch

Kabootie Computey!

New Tinkerer
Sep 13, 2022
15
19
3
www.kabootie.com
Well I'm not quite enough of a H4XX0R to do it in 3 lines, but I trimmed 6,018 whole bytes out of my app by replacing ANSI sprintf() with this bad boy.

C:
void ConcatStr255( StringPtr first, StringPtr second ) {
    Byte *f = first;
    Byte *s = second;
    int fl  = *f;
    *f      = fl + *s;
    BlockMove( s + 1, f + fl + 1, 255 - fl );
}
 

Crutch

Tinkerer
Jul 10, 2022
292
226
43
Chicago
Wow, was I wrong about the smart linker? How was sprintf() taking 6K of code?

Regarding quickie string cat’ing, here’s how I would do it (not tested…. I know I know … and I think totally equivalent to yours)

C:
void StringCat(Str255 a, Str255 b)  // append b onto a
{
    assert(a[0] + b[0] < 256, “barf”);  // omit if you trust yourself to use little strings
    BlockMove(&b[1], &a[a[0] + 1], b[0]);
    a[0] += b[0];
}
 
Last edited:

Kabootie Computey!

New Tinkerer
Sep 13, 2022
15
19
3
www.kabootie.com
C:
void StringCat(Str255 a, Str255 b)  // append b onto a
{
    assert(a[0] + b[0] < 256, “barf”);  // omit if you trust yourself to use little strings
    BlockMove(&b[1], &a[a[0] + 1], b[0]);
    a[0] += b[0];
}
Clever! Am I misreading this, or is there a chance it could stomp over some memory if a+b combined are longer than 255 characters?
 

KennyPowers

Active Tinkerer
Jun 27, 2022
246
289
63
In case anyone experiences the same issue:

I was having trouble getting the "EventTrigger" example program from Macintosh C Programming Primer to successfully send high-level events to the "EventTracker" example program. I triple checked my code as well as EventTracker's SIZE flags and creator signature. I finally tried out ThinkC's debugger for the first time and saw that AESend() was returning a -903 error code. Looking that up in Think Reference said it was a "noPortErr" and not much else. Googling that got me this though. Basically, I had to add "HighLevelEvent-Aware" to EventTrigger's SIZE flags. The book doesn't appear to mention that.
 

pretzelFTW

New Tinkerer
Sep 5, 2022
26
8
3
In the EventTracker code, the EventInit() function is confusing me.

My understanding is that we are using the Gestalt function to check for an operating system feature (Apple Events).

C:
err = Gestalt(gestaltAppleEventsAttr, &feature);

I'm running 6.0.8, and I'm seeing an error returned from this function. The error code is -5551. Looking up that value in the reference, I see it's "gestaltUndefSelectorErr". That kind of makes sense – the 6.0.8 environment wouldn't even know what Apple Events are – never mind support them.

But the way the code is written seems to suggest that the call to Gestalt should succeed and then this comparison would be what fails:

C:
if (!(feature & (kGestaltMask << gestaltAppleEventsPresent)))


Are there environments where the "gestaltAppleEventsAttr" selector is recognized but the Apple Events feature is not present?
 

KennyPowers

Active Tinkerer
Jun 27, 2022
246
289
63
Are there environments where the "gestaltAppleEventsAttr" selector is recognized but the Apple Events feature is not present?

I don't know the answer to that, but maybe the error string drawn after calling Gestalt() ("Problem in calling Gestalt!") is just too general in the interest of not complicating the example program? You could always do something like:

C:
err = Gestalt(gestaltAppleEventsAttr, &feature);

if(noErr != err)
{
    if(gestaltUndefSelectorErr == err) DrawEventString("\pApple events not available!");
    else DrawEventString("\pProblem in calling Gestalt!");
    return;
}

if(!(feature & (kGestaltMask << gestaltAppleEventsPresent)))
{
    DrawEventString("\pApple events not available!");
    return;
}

Defining the duplicated string literal (or all user-facing strings) as a constant or resource would be a good idea in a "real" program of course.
 
Last edited:
  • Like
Reactions: pretzelFTW

Crutch

Tinkerer
Jul 10, 2022
292
226
43
Chicago
Right, I would probably just do

C:
if (noErr != Gestalt(gestaltAppleEventsAttr, &feature)
       || !(feature & (kGestaltMask << gestaltAppleEventsPresent)))
{
    // show error, can't use AppleEvents
}

If Gestalt won’t let you ask about a thing, it’s safe to assume that thing doesn’t exist.

In practice I don’t think Gestalt will ever recognize the AppleEvents selector but not actually have AppleEvents, but this could be true for other system features that, even after being introduced, were not ubiquitous. (Example: the Apple Sound Chip)
 
Last edited:
  • Like
Reactions: pretzelFTW

eric

Administrator
Staff member
Sep 2, 2021
822
1,307
93
MN
scsi.blue
I'm trying to install an event to handle shutdown/restart - from the book it seems like I need to register a method to do the needful. But it seems to never fire when I restart. Also I dont think the events I'm using and referenced in the book are in System 6

1665504576592.png
 

KennyPowers

Active Tinkerer
Jun 27, 2022
246
289
63
I'm trying to install an event to handle shutdown/restart - from the book it seems like I need to register a method to do the needful. But it seems to never fire when I restart. Also I dont think the events I'm using and referenced in the book are in System 6

View attachment 9260
That looks to be doing the same as my EventTracker code (the code from book pretty much), and when I restart with EventTracker running, its kAEQuitApplication handler fires. I'm running 7.1 though. Does AEInstallEventHandler() return noErr for you?