Here is a VR walkthrough of the Branson location:
And here is a video from last year of their original location in Springfield:
One of these locations has the coolest bathroom in the world.
The other just has a boring bathroom.
Here is a VR walkthrough of the Branson location:
And here is a video from last year of their original location in Springfield:
One of these locations has the coolest bathroom in the world.
The other just has a boring bathroom.
Explain it to me like I am five…
Every time I think I have figured out what Google Street View expects, I encounter a new problem. This time, I have a file that Google says has gaps in the GPS data:
But the problem is, I have already tried to fix this multiple times using tools like gpsbabel. This command line utility will create in-between points at the rate you specify. You can use a value of 1 second and get a point every second in the entire GPX file.
Using GPX Editor on the Mac lets me inspect the tracking points. If I sort by duration value, the longest value in the entire file is 4 seconds:
Yet, Google claims there is a 7 second gap after 9 seconds. Looking at the points in time order shows this is not the case: (But do note, this GPS starts sooner than the video; so the points I show here may be from time before the video begins. This does not change the issue, since there is nothing reported longer than 4 seconds in the entire file, anywhere.)
Anyone care to explain why this happens and how I can fix it? I have gotten every other file I have uploaded to work just fine, except for these two I have been working on since last week.
Even when I run gpsbabel with a 1 second “gap” between each point, I still get this type of error.
What else is Google looking for? Web searches and even chatting with Google’s Gemini A.I. have not produced anything helpful.
Comments appreciated…
Here are some comparison videos I recorded last weekend at Silver Dollar City theme park. I mounted both cameras side-by-side and rode their water-based dark ride, The Flooded Mine. Both cameras were recording in 360 video using the low light mode. I then reframed each to a 4K forward view to make this split screen.
There are two segments to this video. One shows them top/bottom, then the video is repeated side-by-side so you can see more of the horizontal and vertical image.
Next is a test of the single lens mode. The X5 was recording in 4K, and the Osmo 360 has some higher 5K-6K resolution mode. For the comparison video, 4K was used so the Osmo is scaled down to fit. This is a walk through Grandfather’s Mansion:
And a week before this, I visited Lost Island Themepark in Waterloo, Iowa. I was recording the indoor queue to their dark ride, Volcano. I was not intentionally doing a comparison of the two cameras, but I did record it twice, each time using a different camera. I put a short clip together showing this:
More to come… Let me know what comparisons you are interested in seeing.
See Also: part 1 and part 2.
In the previous installment, I discussed how lazy I am and shared my general dislike of doing things manually when they can be automated.
So let’s automate…
If you program in C, you are likely familiar with using #define to set a value you can use elsewhere in the code:
#define NUMBER_OF_GUESSES 10
int answer = RandomNumber (100);
for (int try=1; try<=NUMBER_OF_GUESSES; try++)
{
printf ("Try #%d of %d - guess a number from 1-100: ",
try, NUMBER_OF_GUESSES);
guess = InputNumber ();
// ... logic continues ...
}
Now instead of updating multiple places in the file for the number of guesses, only the #define has to be changed. The #define has the advantage of not taking extra code or memory space like a variable would, which is important when you are working on embedded systems with 8K of RAM.
You probably have also seen #defines used for marking code to be included or not included in a program:
#define DEBUG_MODE
void Function ()
{
#if defined(DEBUG_MODE)
printf ("Inside Function()\n");
#endif
// ...logic continues...
}
WIth “#define DEBUG_MODE” there, the printf() will be included. Remove that #define (or comment it out) and it will not.
But #defines can also become macros with parameters, such as this:
#define SNOOZE(x) SleepMs(x*1000)
If you want to sleep for seconds, you could use a macro that turns into the call to the millisecond sleep with the passed-in value multiplied by 1000:
void Function ()
{
printf ("Pausing for 5 seconds...\n");
SNOOZE (5);
// ...logic continues...
}
The C preprocessor will take that “5” and substitute where the “x” is in the replacement text, becoming:
void Function ()
{
printf ("Pausing for 5 seconds...\n");
SleepMs (5*1000);
// ...logic continues...
}
Now if the code is ported to a system with a different sleep call, the #define can be changed to use whatever is available.
I’d expect anyone who has programmed in C has done one or all of these things.
But a macro does not have to be a simple number, string or function name. It can be a whole block of code that gets substituted. You can put in many lines, just by adding a “\” at the end of the line to continue parsing the next line after it:
#define DISPLAY_COUNTDOWN(x) \
for (int idx=x; idx>=0; idx++) \
{ \
printf ("%d...", idx); \
sleep (1000); /* 1000ms, 1 second sleep */
}
void Function ()
{
printf ("Self-destruct activated...\n");
DISPLAY_COUNTDOWN (10);
// ...logic continues ...
}
And that would be processed to replace the “DISPLAY_COUNTDOWN(10)” with the C code in the #define:
void Function ()
{
printf ("Self-destruct activated...\n");
for (int idx=x; idx>=0; idx++) { printf ("%d...", idx); sleep (1000); /* 1000ms, 1 second sleep */ }
// ...logic continues ...
}
Yeah, it would look ugly if you could see how the C preprocessor puts it in, but it builds and runs and you never see it (unless you specifically look at preprocessed output files).
But that is probably dumb. You should just make a “DisplayCountdown()” function and have it be more normal.
But in the case of my panel functions, each one of them had a unique panel name and panel identifier, so using a function for them was not really possible. Each one had to be its own function since the functions contained the name of the panel (“PanelMainInit()”, “PanelMainTerm()”, etc.).
But a #define can do that…
#define GENERATE_PANEL_PROTOTYPES(panel_name) \
int panel_name##Init (void); \
int panel_name##GetHandle (void); \
int panel_name##Display (void); \
int panel_name##Hide (void); \
int panel_name##Term (void);
The macro uses “panel_name” as the substitution “variable” passed in, and will place whatever text is there anywhere in the macro where “panel_main” appears. Since I wanted to pass in the filename (without extension) of the panel such as “PanelMain” or “PanelFaults”) and build a function name out of it, I use the ## concatenate feature that will glue the items before and after it together. That macro used like this:
GENERATE_PANEL_PROTOTYPES(PanelMain)
GENERATE_PANEL_PROTOTYPES(PanelFaults)
GENERATE_PANEL_PROTOTYPES(PanelAdmin)
…effectively generates the prototypes like this:
int PanelMainInit (void);
int PanelMainGetHandle (void);
int PanelMainDisplay (void);
int PanelMainHide (void);
int PanelMainTerm (void);
int PanelFaultsInit (void);
int PanelFaultsGetHandle (void);
int PanelFaultsDisplay (void);
int PanelFaultsHide (void);
int PanelFaultsTerm (void);
int PanelAdminInit (void);
int PanelAdminGetHandle (void);
int PanelAdminDisplay (void);
int PanelAdminHide (void);
int PanelAdminTerm (void);
…though it actually looks like one long run-one line for each one if you looked at the pre-processed C output, but the result is the same.
A similar macro could generate the actual functions:
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define GENERATE_PANEL_FUNCTIONS(panelName, panelResourceID) \
static int S_##panelName##Handle = 0; /* Zero is not a valid panel handle. */ \
\
int panelName##Init (void) \
{ \
int panelHandle = 0; \
if (S_##panelName##Handle <= 0) \
{ \
panelHandle = LoadPanel (0, TOSTRING(panelName)".uir", panelResourceID); \
if (panelHandle > 0) \
{ \
S_##panelName##Handle = panelHandle; \
\
panelName##UserInit (panelHandle); \
} \
} \
else \
{ \
panelHandle = S_##panelName##Handle; \
} \
return panelHandle; \
} \
\
int panelName##GetHandle (void) \
{ \
return panelName##Init (); \
} \
\
int panelName##Display (void) \
{ \
int status = UIEHandleInvalid; \
int panelHandle = panelName##Init (); \
if (panelHandle > 0) \
{ \
status = DisplayPanel (panelHandle); \
} \
return status; \
} \
\
int panelName##Hide (void) \
{ \
int status = UIEHandleInvalid; \
if (S_##panelName##Handle > 0) \
{ \
status = HidePanel (S_##panelName##Handle); \
} \
return status; \
} \
\
/* Unload the panel, if valid. */ \
int panelName##Term (void) \
{ \
int status = UIEHandleInvalid; \
if (S_##panelName##Handle > 0) \
{ \
status = DiscardPanel (S_##panelName##Handle); \
if (status == UIENoError) \
{ \
S_##panelName##Handle = 0; \
} \
} \
return status; \
}
That macro would be used like this:
GENERATE_PANEL_FUNCTIONS(PanelMain, PANEL_MAIN)
GENERATE_PANEL_FUNCTIONS(PanelFaults, PANEL_FAULTS)
GENERATE_PANEL_FUNCTIONS(PanelAdmin, PANEL_ADMIN)
…and it would create a fully populated set of functions for those panels.
This allowed me to have a header file that had those macros, such as “PanelMacros.h”, and then have a .c and .h for each panel, or one big file that had them all in it.
// Panels.h
GENERATE_PANEL_PROTOTYPES(PanelMain);
GENERATE_PANEL_PROTOTYPES(PanelFaults);
GENERATE_PANEL_PROTOTYPES(PanelAdmin);
// Panels.c
GENERATE_PANEL_FUNCTIONS(PanelMain, PANEL_MAIN)
GENERATE_PANEL_FUNCTIONS(PanelFaults, PANEL_FAULTS)
GENERATE_PANEL_FUNCTIONS(PanelAdmin, PANEL_ADMIN)
And it worked great! And, if I later decided I wanted to add debugging output or something else, instead of editing one hundred different panel functions I could just modify the macro. For example:
#define GENERATE_PANEL_FUNCTIONS(panelName, panelResourceID) \
static int S_##panelName##Handle = 0; /* Zero is not a valid panel handle. */ \
\
int panelName##Init (void) \
{ \
int panelHandle = 0; \
if (S_##panelName##Handle <= 0) \
{ \
panelHandle = LoadPanel (0, TOSTRING(panelName)".uir", panelResourceID); \
if (panelHandle > 0) \
{ \
DebugPrintf ("Panel %s loaded.\n", TOSTRING(panelName)); \
S_##panelName##Handle = panelHandle; \
\
panelName##UserInit (panelHandle); \
} \
} \
else \
{ \
DebugPrintf ("Panel %s already initialized.\n", TOSTRING(panelName)); \
There are a few things to unpack in this example, such as the use of macros STRINGIFY(x) and TOSTRING(x), but those probably could be their own blog post.
Anyway, if you are lazy, and faced with generating dozens or hundreds of almost identical functions, this macro approach can save a ton of time. The macros I made for my original project, dealing with message functions, are vastly more complex than these, but I figured if I started with those most would run away screaming. (I know I sure would if I had been presented them by a coworker.)
I am sure there will be more to say about this, so perhaps a part 3 will show up.
Until then, I’d love to hear what an experienced C macro programmer has to say about this. I bet there are some better techniques and things I am completely unaware of. I’d love it if you’d share.
Thanks…
Addendum: Since I began writing this post, I have converted about 50 panels at work using a much more complex set of #define macros. They keep evolving as I needed to add support for “parent/child” panels, or extra debugging, or even new functions to check if a panel is displayed at the moment. All I did was update the macro, and the next build could use the new functions. I expect it has already saved me days of typing…
On Friday I received my DJI OSMO 360 camera from B&H Photo here in the U.S.A. I took it out on a test ride with my Insta360 X5 next to it to try to capture some comparison video. Unfortunately, the quality coming out of the Osmo 360 was inferior, and I learned it defaulted to “Standard” bitrate but had a setting for “High.” Since my X5 was set to “High” bitrate, I believe my first comparison would not have been a fair on.
Because of that, I will be re-doing these tests again, soon.
I also took both the X5 and Osmo 360 to Lost Island Themepark in Waterloo, Iowa yesterday. While I did not do any head-to-head comparisons, I did use the Osmo 360 a few times under low light conditions. It is my understanding that it is a better 360 camera for low light.
I will begin sharing these to my Sub-Etha Software YouTube channel, shortly.
Earlier this week, I read (on REDDIT, I think) about 1.5.6 being released. I checked my X5 and it reported nothing new. I checked again, still nothing. But today, something. If you are the type that likes to update immediately, go for it. Else, give it a week and see if any others have problems with it.
Yesterday, I received a notification from B&H Photo that they had the DJI Osmo 360 camera in stock. Indeed, it seems true, at least at the time of this writing:
https://www.bhphotovideo.com/c/search?q=DJI%20Osmo%20360&sts=ma
This is surprising, since DJI‘s own website has yet shown the item as order-able, at least for folks viewing from the USA.
Meanwhile, Amazon has the camera for sale, but it is through a third-party reseller. Even with it “Shipped by Amazon,” buyers should beware. If this item was not going to be sold in the USA, some reseller could have ordered them from another region to resell here in the States (via eBay, Amazon, etc.). Without knowing if warranties would be honored when purchased this way, or if warranty service/support was possible, I was not willing to order from that reseller. (They may be fine and great, though. I am just speculating.)
So, if you are looking for a well-established place to buy a DJI Osmo 360 in the USA, maybe start with B&H Photo. I have used them a number of times over the past decades, and have yet to have a problem.
Good luck!
ALERT! ALERT! We are doing this wrong. It has been pointed out in a comment to an earlier installment that we missed an important part about what this contest was supposed to produce!
More on that in a moment… But first, let’s look at a faster version of the challenge, created by Dillon Teagan:
10 TIMER=0
20 PMODE4,1:PCLS1:SCREEN1,1
30 L=&HFF:I=-3:DIMR,L,U,D
40 D$="D=D;R=R;U=U;L=L;
50 DRAW"BM0,191C0R=L;U191L=L;
60 FORD=188TO3STEP-6:R=L+I:U=D+I:L=R+I:DRAWD$:NEXT
70 PRINTTIMER/60
This one clocks in at 2.7 seconds, and does some wonderful optimizations!
First, Dillon clearly understands how the BASIC interpreter works. He is doing things like using hex &HFF instead of decimal 255 which makes that bit parse a tad faster. Next, you see him declare a variable, followed by a DIM which pre-declared R, L, U and D. In this example, that DIM probably does not help, but in a real program, you can declare your variables up front and do them in the order of “most accessed” to least. This sets their order in the variable table, so when you try to access one, the one you access the most can be at the top of the list. I’ve posted about this before, but consider this:
10 DIM A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z
20 PRINT Z
30 Z=Z+1
40 IF Z<100 THEN 20
If we truly did have 26 variables in use, and declared them like this, Z would be at the end of the variable table. EVERY time Z is needed, BASIC has to scan through all 26 variables trying to match Z so it can be used. This would be MUCH slower than, if you knew Z was being used the most often, you did this:
10 DIM Z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y
20 PRINT Z
30 Z=Z+1
40 IF Z<100 THEN 20
With that one minor change (declaring Z first), Z is now the first variable in the table and will be found much quicker. Try it sometime.
But I digress…
The next cool optimization Dillon does is by drawing the initial bottom line (bottom left to bottom right, 256 pixels wide) and right side (bottom right to top right, 192 pixels tall) in line 50, along with the first left (top right to top left, 256 pixels wide) before entering the loop.
The loop itself is using a FOR/NEXT loop which is faster than using GOTO since no line scanning has to be done. BASIC stores where the FOR is, and when NEXT is encountered it pops back to that location rather than scanning forward to find the target line, or starting at the first line and scanning forward from there. Nice.
With the first three “full width/height” lines out of the way, the loop is just doing the “minus 3” size of all four lines. That’s clever. The entire draw string is in a string (D$) and I am unsure if this speeds things up versus having it directly in the line itself (line 60).
Impressive. I wish I’d thought of that!
However… we have been doing it wrong, so far, it seems.
In a comment left on part 1 of this series, Paul Fiscarelli pointed out something that I completely got wrong:
Hello Allen – I sent you a DM on FB, but I don’t think you’ve seen it yet. What you have posted above is not exactly an accurate reproduction of what is being asked in the challenge question. You are using an offset decrease of 3 in each of your iterations, which produces a gap of only 2-pixels in both height and width. The challenge is indicating a gap of 3-pixels between lines, which requires an offset decrease of 4 in each iteration. This is further evident in the challenge’s diagram of the spiral, which indicates a line-length of 188-pixels for the line on the far left-hand side of the screen. If you perform a screen grab in XRoar (pixel perfect geometry of 640×480 window and 512×384 screen resolution), you will find your code generates a line length of 189 pixels (scale the 512×384 in half).
If you change the offset decrease in your code to 4 instead of 3, you will achieve a render time of roughly 2.43 seconds. This is due to the fact that you are rendering about 23% fewer lines with the DRAW statement.
You can reduce this time even further if you were to use only a single offset variable for drawing both the horizontal and vertical lines, and by adding a separate width to the horizontal lines with a value of 64 = (256w – 192v). This method will shave roughly 0.10 seconds off your render time, down to approximately 2.33 seconds.
10 TIMER=0
20 PMODE4,1:PCLS:SCREEN1,1
30 OF=191:DRAW”BM0,191″
40 DRAW”R=OF;R64″
50 DRAW”U=OF;L=OF;L64;”
60 OF=OF-4
70 DRAW”D=OF;R=OF;R64;”
80 OF=OF-4
90 IF OF>0 THEN 50
100 TM=TIMER
110 IF INKEY$=”” THEN 110
120 PRINT TM/60As an additional optimization step, you can replace the IF/THEN boundary check with a FOR/NEXT loop, to shave another 0.05 seconds from your render time – getting it down to roughly 2.28 seconds.
10 TIMER=0
20 PMODE4,1:PCLS:SCREEN1,1
30 OF=191:DRAW”BM0,191″
40 DRAW”R=OF;R64″
50 FOR N=0 TO 23
60 DRAW”U=OF;L=OF;L64;”
70 OF=OF-4
80 DRAW”D=OF;R=OF;R64;”
90 OF=OF-4
100 NEXT
110 TM=TIMER
120 IF INKEY$=”” THEN 120
130 PRINT TM/60There are probably some other optimizations that can be done here – but it’s a start. Oh, I also tested these examples with VCC 2.1.9.2-pre2. I’m sure there will be slight variations in timing with the different emulators, and probably even with different versions of VCC.
– Paul Fiscarelli
So, uh … oops. It seems obvious now:
Let’s regroup, and take a look at Paul’s versions in the next installment.
Until then… RTFM!
See Also: part 1 and part 2.
There is always a first time for everything, and my first time doing this was a few years ago with a day job task. I was going to create a Windows DLL (dynamic link library) that would handle hundreds of messages (write/read response) via the I2C protocol.
The message protocol featured a header that contained a Message Type and Command Code. There were several types of messages, but for this blog post let’s just look at two types:
The Command Code was a byte, which meant there could be up to 256 (0-255) commands per message type. For example, some DATA messages might be defined like this:
#define DATA_PING 0 // ACK if there, NAK if not
#define DATA_RESET_CPU 1 // Reset CPU
#define DATA_EXPLODE 2 // Cause the penguin on the TV set to explode.
And QUERY messages might be like:
#define QUERY_STATUS 0 // Returns a status response
#define QUERY_VOLTAGE 1 // Returns the voltage
#define QUERY_TEMPERATURE 2 // Returns the temperature
These are, of course, made up examples, but you get the idea.
We had a number of different board types on our system that could receive these messages. Some messages were only applicable to specific boards (like, you couldn’t get temperature from a board that did not have a thermometer circuit or whatever). My idea was to create simple C functions that represented the message sent to the specific board, like this:
resp = AlphaBoardPING ();
resp = BetaBoardPING ();
resp = DeltaBoardPING ();
...
resp = BetaBoardRESET_CPU ();
...
resp = DeltaBoardQUERY_STATUS (...);
…and so on. I thought it would make a super simple way to write code to send messages and get back the status or response payload or whatever.
This is what prompted me to write a post about returning values as full structures in C. That technique was used there, making it super simple to use (and no chance of a wrong pointer crashing things).
I experimented with these concepts on my own time to make sure this idea would work. Some of the things I did ended up on my GitHub page:
But, as I like to point out, I am pretty lazy and hate doing so much typing. The thought of creating hundreds of functions by hand was not how I wanted to spend my time. Instead, I wanted to find a way to automate the creation of these functions. After all, they all followed the same pattern:
The only thing custom would be what Message Type and Command Code to put in, and if there was a payload to send, populating those bytes with the appropriate data.
When a response was received, it would be parsed based on the Message Type and Command Code, and return a C structure matching the response payload.
Initially, I thought about making a program or script that would spit out hundreds of functions. But, this not lazy enough. Sure, I could have done this and created hundreds of functions, but what if those functions needed a change later? I’d have to update the program that created the functions and regenerate all the functions all over again.
There has to be a better lazier way.
I realized I could make a set of #define macros that could insert proper C code or prototypes. Then, if I ever needed to change something, I only had to change the macro. There would be no regeneration needed, since the next compile would use the updated macro. Magic!
It worked very well, and created hundreds and hundreds of functions without me ever having to type more than the ones in the macro.
It worked so well that I ended up using this approach very recently for another similar task I was far too lazy to do the hard way. I thought I would share that much simpler example in case you are lazy as well.
At work we use LabWindows/CVI, a Windows C compiler with its own GUI. It has a GUI editor where you create your window with buttons and check boxes and whatever, then you use functions to load the panel, display it, and hide it when done. They look like this:
int panelHandle = LoadPanel (0, "PanelMAIN.uir", PANEL_MAIN);
DisplayPanel (panelHandle);
// Do stuff...
HidePanel (panelHandle);
DiscardPanel (panelHandle);
Then, when you interact with the panel, you have callback functions (if the user clicks the “OK” button, it jumps to a function you might name “UserClickedOKButtonCallback()” or whatever.
If you need to manipulate the panel, such as changing the status of a Control (checkbox, text box, or whatever), you can set Values or Attributes of those Controls.
SetCtrlVal (panelHandle, PANEL_MAIN_OK_BUTTON, 1);
SetCtrlAttribute (panelHandle, PANEL_MAIN_CANCEL_BUTTON, ATTR_DIMMED, 1);
It is a really simple system and one that I, as a non-windows programmer who had never worked with GUIs before, was able to pick up and start using quickly.
One of the issues with this setup is that you had to have the panel handle in order to do something. If a message came in from a board indicating there was a fault, that code might need to toggle on some “RED LED” graphics on a GUI panel to indicate the faulted condition. But, that callback function may not have any of the panel IDs. The designed created a lookup function to work around this:
int mainPanelHandle = LookUpPanelHandle(MAIN_PANEL);
SetCtrlVal (mainPanelHandle, PANEL_MAIN_FAULT_LED, 1);
A function similar to that was in the same C file where all the panels were loaded. Their handles saved in variables, then the LookUp function would go through a huge switch/case with special defines for every panel and return the actual panel handle that matched the define passed in.
It worked great but it was slower since it had to scan through that list every time we wanted to look up a panel. At some point, all the panel handles were just changed to global variables so they could be accessed quickly without any lookup:
SetCtrlVal (g_MainPanelHandle, PANEL_MAIN_FAULT_LED, 1);
This also worked great, but did not work from threads that did not have access to the main GUI context. Since I am not a Windows programmer, and have never used threads on any embedded systems, I do not actually understand the problem (but I hear there are “thread safe” variables that can be used for this purpose).
Instead of learning those special “thread safe” techniques, I decided to create a set of self-contained panel functions so you could do things like this:
int mainPanelHandle = PanelMainInit (); // Load/init the main panel.
PanelMainDispay (); // Display the panel.
SetCtrlVal (mainPanelHandle, PANEL_MAIN_FAULT_LEFT, 1);
...
PanelMainHide ();
...
PanelMainTerm (); // Unload main panel and release the memory.
When I needed to access a panel from another routine, I would use a special function that returned the handle:
int panelMainHandle = PanelMainGetHandle ();
SetCtrlVal (mainPanelHandle, PANEL_MAIN_FAULT_LEFT, 1);
I even made these functions automatically Load the panel if needed, meaning a user could just start using a panel and it would be loaded on-demand if was not already loaded. Super nice!
Here is a simple version of what that code looks like:
static int S_panelHandle = 0; // Zero is not a valid panel handle.
int PanelMainInit (void)
{
int panelHandle = 0;
if (S_panelHandle <= 0) // Zero is not a valid panel handle.
{
panelHandle = LoadPanel (0, "PanelMAIN.uir", PANEL_MAIN);
// Only set our static global if this was successful.
if (panelHandle > 0) // Zero is not a valid panel handle.
{
S_panelHandle = panelHandle;
}
}
else // S_panelHandle was valid.
{
panelHandle = S_panelHandle;
}
// Return handle or status in case anyone wants to error check.
return panelHandle;
}
int PanelMainGetHandle (void)
{
// Return handle or status in case anyone wants to error check.
return PanelMainInit ();
}
int PanelMainTerm (void)
{
int status = UIEHandleInvalid;
if (S_panelHandle > 0) // Zero is not a valid panel handle.
{
status = DiscardPanel (S_panelHandle);
if (status == UIENoError)
{
S_panelHandle = 0; // Zero is not a valid panel handle.
}
}
// Return status in case anyone wants to error check.
return status;
}
int PanelMainDisplay (void)
{
int status = UIEHandleInvalid;
int panelHandle;
panelHandle = PanelMainInit (); // Init if needed.
if (panelHandle > 0) // Zero is not a valid panel handle.
{
status = DisplayPanel (panelHandle);
}
// Return status in case anyone wants to error check.
return status;
}
int PanelMainHide (void)
{
int status = UIEHandleInvalid;
if (S_panelHandle > 0) // Zero is not a valid panel handle.
{
status = HidePanel (S_panelHandle);
}
// Return status in case anyone wants to error check.
return status;
}
This greatly simplified dealing with the panels. Now they could “just be used” without worrying about loading, etc. There was no long Look Up table, and no global variables. The only places the panel handles were kept was inside the file where the panel’s functions were.
Nice and simple, and it worked even greater than the first two attempts.
…until you have to make a hundred of these functions…
…and then decide you need to change something and have to make that change in a hundred functions.
My solution was to use #define macros to generate the code and prototypes, then I would only have to change the macro to alter how all the panels works. (Spoiler: This worked even greater than the previous greater.)
In part 2, I will share a simple example of how this works. If you are lazy enough, you might actually find it interesting.
Until then…
A long, long time ago, I learned about malloc() in C. I could make a buffer like this:
char *buffer = malloc (1024);
…use it, then release it when I was done like this:
free (buffer);
I have discussed malloc here in the past, including a recent post about an error checking malloc I created to find a memory leak.
But today, the Bing CoPilot A.I. suggested I use calloc instead.
And I wasn’t even sure I even remembered this was a thing. And it has been there since before C was standardized…
void* calloc (size_t num, size_t size);
malloc() will return a block of memory and, depending on the operating system and implementation in the compiler, that memory may have old data in it. Hackers were known to write programs that would allocate blocks of memory then inspect it to see what they could find left over from another program previously using it.
Hey, everybody’s gotta have a hobby…
calloc() is a “clear allocation” where it will initialize the memory it returns to zero before returning it to you. It also takes two parameters instead of just one. While malloc() wants to know the number of bytes you wish to reserve, calloc() wants to how know many things (bytes, structures, etc.) you want to reserve, and the size of each thing.
To allocate 1024 bytes using calloc() you would use:
char *buffer = calloc (1, 1024);
…and get one thing of 1024 bytes. Or maybe you prefer reversing that:
char *buffer = calloc (1024, 1);
…so you get 1024 things that are 1 byte each.
Either way, what you get back is memory all set to zeros.
calloc() was suggested by the A.I. because I was allocating a set of structures like this:
// Typedefs
typedef struct
{
int x;
int y;
int color;
} MyStruct;
MyStruct *array = malloc (sizeof(MyStruct) * 42);
The A.I. saw that, and suggested calloc() instead, like this:
MyStruct *array = calloc (42, sizeof(MyStruct));
I do think that looks a bit cleaner and more obvious, if you are familiar with calloc(), and as long as you don’t need the extra speed (setting that memory to 0 should take more time than not doing that), it seems like something to consider.
And maybe that will break me (and other programmers who wrote code before me that I may one day maintain) from doing it manually like…
char *ptr = malloc (1024);
memset (ptr, 0x0, 1024);
I wonder if I will even remember this the next time I need to malloc() something.
I mean calloc() something.
Whatever.
Until then…