lite-C structs |
Top Previous Next |
Let's imagine that one day, your boss comes to you and says: "Jimmy (put your own name here), we have been contacted by a governmental agency. They have heard about your lite-C proficiency and they want YOU to create their next agent database application". Thrilled, you look in the mirror and say to yourself with a low voice: "could this be it? Could this be my first chance to create a project that gets me 100 bucks?"
So you sit down at the computer and start typing; 30 minutes later, you've got the first project ready. Your (not so clever) boss insists that you should explain HIM how the code works, so that HE can present it to the Bee Eye Agency. What can you do? You're only a junior programmer, so you have to comply; you show him the code (structs1.c) and start explaining it to him:
STRING* name_str = "#50"; // holds up to 50 characters STRING* specialties_str = "#20"; // holds up to 20 characters STRING* status_str = "#10"; // holds up to 10 characters
typedef struct { char* name; var age; char* specialties; var success_rate; // 0...100 BOOL active; // active / inactive } AGENTS;
Dear boss, a struct is a mixture of data put together under the same roof, in order to make the things easier for the programmer: it can contain all sorts of variables, strings, entities, pointers and so on. In fact, even the lite-C entities are structs; they contain properties like x, y, z, pan, tilt, roll, skill1... skill100, flag1, etc. The struct above is named AGENTS.
AGENTS* dreamer = { name = "Michael Brent"; age = 24; specialties = "Specops"; success_rate = 84; active = TRUE; // TRUE or FALSE }
Now we have created one of the AGENTS and we have named him dreamer. As you can see, his real name is Michael Brent and we have populated all his struct fields with values. It's not mandatory to fill in all the fields, though. Oh, and note that we have defined a BOOL variable named "active" that can be either TRUE or FALSE; it will help us specify if the agent is active or not.
PANEL* output_pan = { digits (280, 120, "Agent name: %s ", *, 1, name_str); digits (280, 135, "Age: %.0f", *, 1, dreamer.age); digits (280, 150, "Specialties: %s ", *, 1, specialties_str); digits (280, 165, "Success rate percentage (0...100): %.0f", *, 1, dreamer.success_rate); digits (280, 180, "Status: %s ", *, 1, status_str); flags = SHOW; }
The panel above displays all the information on the screen; there's nothing special about it.
function main() { str_cpy(name_str, dreamer.name); str_cpy(specialties_str, dreamer.specialties); if (dreamer.active == TRUE) { str_cpy(status_str, "ACTIVE"); } else { str_cpy(status_str, "INACTIVE"); } }
Function main copies the strings from our structure to external strings (name_str, specialties_str, status_str) so that we can display them using the output_pan panel. The panel "digits" instruction doesn't work with pointers, so we can't use something like dreamer.name in a "digits" definition. Finally, we set status_str to "ACTIVE" or "INACTIVE", depending on the value of dreamer.active. That's all!
Your boss is very excited; he thinks that he has understood some of the inner works of your program (it's just an illusion, though). He takes your application, runs it in front of the Bee Eye Agency officials and they are very impressed; some of them are so surprised that they fall off their comfy chairs! Take a look at the screen yourself - who wouldn't be impressed?
The officials want to hire your services immediately; the first thing that they want from you is to add the addresses of the agents to the same struct (they need to know how to contact the agents, right?). Oh, and they also want to be able to read the data for up to 10,000 agents from an agents.txt file. The pay is beyond your expectations (120 bucks!) so you get down to work right away.
Several weeks later, you are still scratching your head, trying to figure up what's wrong with this tiny snippet (structs2.c):
STRING* address_str = "#100"; // holds up to 100 characters
typedef struct { STRING* address; } AGENTS;
AGENTS taskforces[10000];
PANEL* output_pan = { digits (280, 135, "Address: %s ", *, 1, address_str); flags = SHOW; }
function main() { var file_handle; file_handle = file_open_read("agents1.txt"); file_str_read(file_handle, taskforces[0].address); file_close(file_handle); str_cpy(address_str, taskforces[0].address); }
You have managed to reproduce the bug using these few lines of code; you define a struct that has a single STRING*, and then you define "taskforces", which can store up to 10000 agents, as indicated. Function main opens the agents1.txt file, reads the first address from the agents1.txt file and assigns it to taskforces[0].address. The txt file is closed, and then the string is copied to address_str, in order to be displayed on the screen.
And here's the ugly error message that has been haunting you for weeks:
An empty pointer in main? Why? If you comment the red line of code the error goes away, but you need that line because it is supposed to read the address from the agents1.txt file below!
"Those 120 bucks are never going to be mine!" Tired and disgusted of yourself you look for a solid rope, decided to put an end to your misery. You feel so... NULL! All of the sudden, a small light illuminates a tiny area of your brain. Could it be that the "address" string inside the struct is NULL, so it can't store the data that is read from the agents1.txt file? You hurry back to your computer and start typing in a frenzy...
STRING* address_str = "#100"; // holds up to 100 characters
typedef struct { STRING* address; } AGENTS;
AGENTS taskforces[10000];
PANEL* output_pan = { digits (280, 135, "Address: %s ", *, 1, address_str); flags = SHOW; }
function main() { var file_handle; file_handle = file_open_read("agents1.txt"); taskforces[0].address = str_create(""); file_str_read(file_handle, taskforces[0].address); file_close(file_handle); str_cpy(address_str, taskforces[0].address); }
Yesss! The new, green line of code creates a string at runtime and copies its empty content to "address", replacing the NULL string (practically nothing!) with an empty string, which is something totally different. Initializing the struct members after their creation is a lesson that you will remember for a long time...
Everything works fine now, so you can read the address without problems.
Now you can finally continue the work! The deadline is very close, but you work without sleeping and eating for two days and you manage to finish this (struct3.c) beautiful application that does exactly what was requested from you.
Your boss is amazed! He can use the up / down arrow keys to navigate through the agents' list; the data is read from agents2.txt, a regular text file that can be edited easily. He wants to learn more about this project as well.
var taskforce_age; var taskforce_success; var i = 0;
STRING* name_str = "#50"; // holds up to 50 characters STRING* specialties_str = "#20"; // holds up to 20 characters STRING* status_str = "#10"; // holds up to 10 characters STRING* address_str = "#100"; // holds up to 100 characters STRING* temp_str = "#20"; // just a temporary string
typedef struct { STRING* name; var age; STRING* specialties; var success_rate; // 0...100 BOOL active; // active / inactive STRING* address; } AGENTS;
AGENTS taskforces[10000];
PANEL* output_pan = { digits (220, 120, "Agent name: %s ", arial_font, 1, name_str); digits (220, 145, "Age: %.0f", arial_font, 1, taskforce_age); digits (220, 170, "Specialties: %s ", arial_font, 1, specialties_str); digits (220, 195, "Success rate percentage (0...100): %.0f", arial_font, 1, taskforce_success); digits (220, 220, "Status: %s ", arial_font, 1, status_str); digits (220, 245, "Address: %s ", arial_font, 1, address_str); flags = SHOW; }
The interesting things happen inside function main; we open the agents2.txt file, and then we read all the data from it until we reach its end (until file_str_read returns -1). The while loop below can run until it reaches 10,000 but will probably stop earlier (unless you put 10,000 records inside agents2.txt).
function main() { var file_handle; file_handle = file_open_read("agents2.txt"); while (i < 10000) { taskforces[i].name = str_create(""); if (file_str_read(file_handle, taskforces[i].name) == -1) // the end of file was reached? { break; // then get out of this loop! } else { file_str_read(file_handle, temp_str); taskforces[i].age = str_to_num(temp_str); taskforces[i].specialties = str_create(""); file_str_read(file_handle, taskforces[i].specialties); file_str_read(file_handle, temp_str); taskforces[i].success_rate = str_to_num(temp_str); file_str_read(file_handle, temp_str); if (str_cmpi(temp_str, "TRUE")) { taskforces[i].active = TRUE; } else { taskforces[i].active = FALSE; } taskforces[i].address = str_create(""); file_str_read(file_handle, taskforces[i].address); i++; } } file_close(file_handle); }
All the data from agents2.txt (including the numerical values) is read using file_str_read, and then converted to numerical values when needed; it's always wiser (and much safer) to do so. The BOOL value is read as a string and if it is "TRUE", we set taskforces[i].active to TRUE; otherwise, we set taskforces[i] to FALSE. As soon as the while loop stops running, we've got all the needed data stored inside the taskforces struct and the agents2.txt file is closed.
function info_startup() { wait (-1); // wait until all the data is loaded var counter; while (1) { if (key_cud) { while (key_cud) {wait (1);} counter++; } if (key_cuu) { while (key_cuu) {wait (1);} counter--; } counter = clamp(counter, 0, i-1); str_cpy(name_str, taskforces[counter].name); taskforce_age = taskforces[counter].age; str_cpy(specialties_str, taskforces[counter].specialties); taskforce_success = taskforces[counter].success_rate; if (taskforces[counter].active == TRUE) { str_cpy(status_str, "ACTIVE"); } else { str_cpy(status_str, "INACTIVE"); } str_cpy(address_str, taskforces[counter].address); wait (1); } }
Function info_startup( ) displays the agents' data. We wait one second, making sure that all the data is loaded even if the computer has an ancient hard drive, and then we increase or decrease a variable named counter, depending on the status of the "cursor down" and "cursor up" keys. The "clamp" instruction limits the counter value in the useful range (the "taskforces" elements that hold the useful data), and then we copy the proper values to the variables and strings that will be used by the "digits" instructions.
The boss is happy once again; he didn't understand a thing, but he nods his head and smiles at you pretending that he knows what you're talking about. A few happy days pass by and you are happily dreaming about your 120 bucks (they will be paid in four annual installments).
Good news! Your boss has told you that the Bee Eye guys have contacted him again. They were watching this detective series on TV one day and they saw a monitor who was displaying some agents named Spider, Strider and so on as rotating 3D characters. Can this be created? If the answer is affirmative, they would like to have a 3D calendar with their top 100 secret agents and release it as freeware on popular download sites as a way of promoting their agency. The pay is awesome (125 bucks!) so you get down to work right away.
FONT* arial_font = "Arial#20";
var i = 0; // used as an index var active_agents = 0; // removes the old agent models that aren't used anymore
STRING* name_str = "#30"; // used to display the name of the agent, holds up to 30 characters
TEXT* models_txt = { strings = 100; // store up to 100 model names }
PANEL* output_pan = { digits (120, 120, "Agent name: %s ", arial_font, 1, name_str); flags = SHOW; }
You are going to go the extra mile and create a great calendar for these guys; they'll just have to drop a new 3D model in the project folder and it will be loaded automatically. On top of that, the name of the model will be read from the file name itself. First of all, you create a text that has 100 strings; it will be loaded with the names of the agents.
typedef struct { STRING* name; ENTITY* agent_model; } AGENTS;
AGENTS* taskforces[100];
The code above is a typical struct definition; it contains a string pointer (the name of the agent) and an entity pointer (the model that will be used for the agent). Then, we define a pointer to the AGENTS struct and we name it taskforces; it will be able to store the data for up to 100 agents.
function main() { var counter = 0; level_load (NULL); int j; for(j = 0; j < 100; j++) taskforces[j] = malloc(sizeof(AGENTS)); txt_for_dir(models_txt, "*.mdl"); // loads the model names while (str_len((models_txt.pstring)[i]) > 0) { i++; } i--; while (1) { active_agents = 0; // remove the previous agent model wait (2); // give it enough time to be removed active_agents = 1; // keep the new agent alive taskforces[i] = create_agent((models_txt.pstring)[counter]); str_cpy(name_str, taskforces[i].name); while (!key_cud && !key_cuu) {wait (1);} counter += key_cud - key_cuu; counter = clamp(counter, 0, i); while (key_cud || key_cuu) {wait (1);} } }
Function main starts by loading an empty level; we need a 3D environment if we want to display 3D models. The following 3 lines of code help us allocate memory for our "taskforces" agents from the heap, a block of memory that was created especially for this purpose. We need to do that whenever we are working with huge data: local arrays, entities, and so on; otherwise, we might crash the engine because it uses a limited memory space for local data. This demo would run fine without needing allocated memory, but it's better to be safe than sorry.
The following line of code stores all the model names inside the models_txt strings. The small while loop runs until it encounters a string with a length of zero; when this happens, we have reached the end of the model list. We decrement "i" because it was incremented once more before we got out of the loop; now our index variable (i) stores the number of useful models.
The following while loop sets active_agents to zero for 2 frames. If an agent model is visible on the screen, it will be removed; setting active_agents to 1 again will keep the new agent model that will be created on the screen.
The following line inside the loop looks a bit more complicated, so let's drop it here:
taskforces[i] = create_agent((models_txt.pstring)[counter]);
This line of code might look complicated, but it is quite simple: it calls the create_agent function, which will fill in the fields of taskforces[i]. The function takes a single parameter - the name of the model that was stored in our models_txt string. As a conclusion, a typical execution of the line above would look like this:
taskforces[2] = create_agent("Mummy.mdl"); // if Mummy.mdl would be the 2nd model read from the project's folder
The rest of the lines inside the while loop copy the name of the model to the screen, wait until we press key_cuu or key_cud, incrementing or decrementing the "counter" variable, limit the value of "counter" in the useful range and wait until we release the key_cuu and key_cud keys.
function create_agent(STRING *agent_name) { AGENTS* temp_agent = malloc(sizeof(AGENTS)); temp_agent.name = str_create(agent_name); temp_agent.agent_model = ent_create(agent_name, vector(130, 0, -10), rotate_agent); // create the agent model str_trunc(temp_agent.name, 4); // now cut its name tail (.mdl) because we don't want to display it return temp_agent; // returns the address of the struct for further use (might be needed in the future) }
Function create_agent fills in the fields of our struct. We define an agent named "temp_agent" and (once again) we reserve memory for it from the heap. We set the agent name using str_create and the string that was passed to the function (we've learned that we have to do that the hard way at our previous project), and then we create the 3D model with the given name at X = 130, Y = 0, Z = -10 and we assign it the function named rotate_agent. Finally, we cut the last four characters from agent's name because we'd like it to be displayed as "Hero", not as "Hero.mdl" on the screen. The last line of code returns the address of the struct for further use; we don't do anything with it here, but it could come handy in the future.
function rotate_agent() { while (active_agents) { my.pan += 4 * time_step; wait (1); } ent_remove(my); // remove the agent model when it isn't needed anymore }
This last tiny function simply rotates the model around its pan angle for as long as active_agents isn't set to zero. As you know, we are setting active_agents to zero for 2 frames when we create a new model, in order to remove the old one.
The Bee Eye guys are thrilled! The application does exactly what they wanted and more! They will definitely do business with you in the future, they say. By the time you have finished writing this article, the money from the Agency has arrived. Your boss takes 72% (as agreed) but guess who is writing the lite-C application that does the calculation...
|