I'm working on a texture management and animation solution for a small side project of mine. Although the project uses Allegro for rendering and input, my question mostly revolves around C and memory management. I wanted to post it here to get thoughts and insight into the approach, as I'm terrible when it comes to pointers.
Essentially what I'm trying to do is load all of my texture resources into a central manager (textureManager) - which is essentially an array of structs containing ALLEGRO_BITMAP objects. The textures stored within the textureManager are mostly full sprite sheets.
From there, I have an anim(ation) struct, which contains animation-specific information (along with a pointer to the corresponding texture within the textureManager).
To give you an idea, here's how I setup and play the players 'walk' animation:
createAnimation(&player.animations[0], "media/characters/player/walk.png", player.w, player.h);
playAnimation(&player.animations[0], 10);
Rendering the animations current frame is just a case of blitting a specific region of the sprite sheet stored in textureManager.
For reference, here's the code for anim.h and anim.c. I'm sure what I'm doing here is probably a terrible approach for a number of reasons. I'd like to hear about them! Am I opening myself to any pitfalls? Will this work as I'm hoping?
anim.h
#ifndef ANIM_H
#define ANIM_H
#define ANIM_MAX_FRAMES 10
#define MAX_TEXTURES 50
struct texture {
bool active;
ALLEGRO_BITMAP *bmp;
};
struct texture textureManager[MAX_TEXTURES];
typedef struct tAnim {
ALLEGRO_BITMAP **sprite;
int w, h;
int curFrame, numFrames, frameCount;
float delay;
} anim;
void setupTextureManager(void);
int addTexture(char *filename);
int createAnimation(anim *a, char *filename, int w, int h);
void playAnimation(anim *a, float delay);
void updateAnimation(anim *a);
#endif
anim.c
void setupTextureManager() {
int i = 0;
for(i = 0; i < MAX_TEXTURES; i++) {
textureManager[i].active = false;
}
}
int addTextureToManager(char *filename) {
int i = 0;
for(i = 0; i < MAX_TEXTURES; i++) {
if(!textureManager[i].active) {
textureManager[i].bmp = al_load_bitmap(filename);
textureManager[i].active = true;
if(!textureManager[i].bmp) {
printf("Error loading texture: %s", filename);
return -1;
}
return i;
}
}
return -1;
}
int createAnimation(anim *a, char *filename, int w, int h) {
int textureId = addTextureToManager(filename);
if(textureId > -1) {
a->sprite = textureManager[textureId].bmp;
a->w = w;
a->h = h;
a->numFrames = al_get_bitmap_width(a->sprite) / w;
printf("Animation loaded with %i frames, given resource id: %i\n", a->numFrames, textureId);
} else {
printf("Texture manager full\n");
return 1;
}
return 0;
}
void playAnimation(anim *a, float delay) {
a->curFrame = 0;
a->frameCount = 0;
a->delay = delay;
}
void updateAnimation(anim *a) {
a->frameCount ++;
if(a->frameCount >= a->delay) {
a->frameCount = 0;
a->curFrame ++;
if(a->curFrame >= a->numFrames) {
a->curFrame = 0;
}
}
}
You may want to consider a more flexible Animation structure that contains an array of Frame structures. Each frame structure could contain the frame delay, an x/y hotspot offset, etc. This way different frames of the same animation could be different sizes and delays. But if you don't need those features, then what you're doing is fine.
I assume you'll be running the logic at a fixed frame rate (constant # of logical frames per second)? If so, then the delay parameters should work out well.
A quick comment regarding your code:
textureManager[i].active = true;
You probably shouldn't mark it as active until after you've checked if the bitmap loaded.
Also note that Allegro 4.9/5.0 is fully backed by OpenGL or D3D textures and, as such, large bitmaps will fail to load on some video cards! This could be a problem if you are generating large sprite sheets. As of the current version, you have to work around it yourself.
You could do something like:
al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP);
ALLEGRO_BITMAP *sprite_sheet = al_load_bitmap("sprites.png");
al_set_new_bitmap_flags(0);
if (!sprite_sheet) return -1; // error
// loop over sprite sheet, creating new video bitmaps for each frame
for (i = 0; i < num_sprites; ++i)
{
animation.frame[i].bmp = al_create_bitmap( ... );
al_set_target_bitmap(animation.frame[i].bmp);
al_draw_bitmap_region( sprite_sheet, ... );
}
al_destroy_bitmap(sprite_sheet);
al_set_target_bitmap(al_get_backbuffer());
To be clear: this is a video card limitation. So a large sprite sheet may work on your computer but fail to load on another. The above approach loads the sprite sheet into a memory bitmap (essentially guaranteed to succeed) and then creates a new, smaller hardware accelerated video bitmap per frame.
Are you sure you need a pointer to pointer for ALLEGRO_BITMAP **sprite; in anim?
IIRC Allegro BITMAP-handles are pointers already, so there is no need double-reference them, since you seem to only want to store one Bitmap per animation.
You ought to use ALLEGRO_BITMAP *sprite; in anim.
I do not see any other problems with your code.
Related
In the following example, I am taking a struct that occupies 32 bytes in memory and writing that to a file and reading it back in -- i.e., serializing the data to a binary format:
#include <stdio.h>
typedef struct _Person {
char name[20];
int age;
double weight;
} Person;
int main(void)
{
Person tom = (Person) {.name="Tom", .age=20, .weight=125.0};
// write the struct to a binary file
FILE *fout = fopen("person.b", "wb");
fwrite(&tom, sizeof tom, 1, fout);
fclose(fout);
// read the binary data and set the person to that
Person unknown;
FILE *fin = fopen("person.b", "rb");
fread(&unknown, sizeof unknown, 1, fin);
fclose(fin);
// confirm all looks ok
printf("{name=%s, age=%d, weight=%f}", unknown.name, unknown.age, unknown.weight);
}
Note however that these are all values on the stack, and no pointers/indirection is involved. How might data be serialized to a file when, for example, multiple pointers can be involved, multiple variables may point to the same memory location, etc. Is this effectively what protocol buffers do?
Ok so you want a binary file. I used to do it this way long ago. It's fine. It just breaks when you move to another platform or bitness. I'm teaching the old way because it's a good place to start. Newer ways are popular now because they keep working when changing platforms or bitnesses.
When writing records to the file I would use structs like this:
typedef struct _Person {
char name[20];
int age;
double weight;
} Person;
typedef struct _Thing {
char name[20];
};
typedef struct _Owner {
int personId;
int thingId;
} Owner;
See how the Owner structure has Id members. These are just indices into the arrays of the other structures.
These can be written out to a file one after another, usually prefixed by a single integer written directly that says how many records of each kind. The reader just allocates an array of structs with malloc big enough to hold them. As we add more items in memory we resize the arrays with realloc. We can (and should) also mark for deleting (say by setting the first character of name to 0) and reusing the record later.
The writer looks something like this:
void writeall(FILE *h, Person *allPeople, int nPeople, Thing *allThings, int nThings, Owner *allOwners, int nOwners)
{
// Error checking omitted for brevity
fwrite(&nPeople, sizeof(nPeople), 1, h);
fwrite(allPeople, sizeof(*allPeople), nPeople, h);
fwrite(&nThings, sizeof(nThings), 1, h);
fwrite(allThings, sizeof(*allThings), nThings, h);
fwrite(&nOwners, sizeof(nOwners), 1, h);
fwrite(allOwners, sizeof(*allOwners), nOwners, h);
}
The reader in turn looks like this:
int writeall(FILE *h, Person **allPeople, int *nPeople, int *aPeople, Thing **allThings, int *nThings, int *aThings, Owner **allOwners, int *nOwners, int *aOwners)
{
*aPeople = 0; // Don't crash on bad read
*aThigns = 0;
*aOwners = 0;
*allPeople = NULL;
*allThings = NULL;
*allOwners = NULL;
if (1 != fread(nPeople, sizeof(*nPeople), 1, h)) return 0;
*allPeople = malloc(sizeof(**allPeople) * *nPeople);
if (!allPeople) return 0; // OOM
*aPeople = *nPeople;
if (*nPeople != fread(*allPeople, sizeof(**allPeople), nPeople, h)) return 0;
if (1 != fread(nThings, sizeof(*nThings), 1, h)) return 0;
*allThings = malloc(sizeof(**allThings) * *nThings);
if (!allThings) return 0; // OOM
*aThings = *nThings;
if (*nThings != fread(*allThings, sizeof(**allThings), nThings, h)) return 0;
if (1 != fread(nOwners, sizeof(*nOwners), 1, h)) return 0;
*allOwners = malloc(sizeof(**allOwners) * *nOwners);
if (!allOwners) return 0; // OOM
*aOwners = *nOwners;
if (*nOwners != fread(*allOwners, sizeof(**allOwners), nOwners, h)) return 0;
return 1;
}
There was an old technique for writing a heap arena directly to disk and reading it back again. I recommend never using it, and never storing pointers on disk. That way lies security nightmares.
When memory was cheap I would have talked about how to use block allocation and linked blocks to dynamically update the on-disk records partially; but now for problems you're going to encounter at this level I say don't bother, and just read the whole thing into RAM and write it back out again. Eventually you will learn databases, which handles that stuff for you.
I am using dwm (6.2) window manager and I found a bug which I would love to solve.
Window manager uses "master area" and "stack area" where windows are put:
It is possible to move window at the top of the "stack area" to the bottom of the "master area" using ALT + i. It is also possible to move windows from the bottom of the "master area" back to the top of "stack area" using ALT + d.
Now in this case, if I use ALT + i, layout changes and after the key combination there are two windows in the "master area":
I repeat it again and now there are three windows in the "master area":
I repeat it yet again and now there are three windows in the "master area" which has 100% width:
If I would at this point decide to return windows from the "master area" to the "stack area" I would start pressing ALT + d and windows would imediately return back to the "stack area". This works okay.
But I intentionaly make a mistake and instead press ALT + i again for example three more times. It looks like nothing happens...
But now if I try to return windows from the "master area" to the "stack area" I first need to press ALT + d three more times and nothing will happen! And then finaly, when I press ALT + d for the fourth time, window manager will return the first window from the bottom of the "master area" to the top of the "stack area".
So this is not well thought out and should be considered a bug...
There must be some sort of a counter in the source code which was incremented three more times by pressing ALT + i but it should not increase after all windows are already in the "master area".
In config.def.h source file (www) there is a part of the code where keys are assigned. And here I can see that when user presses ALT + i function incnmaster() is called and is passed an argument .i = +1 (I don't understand this argument).
static Key keys[] = {
/* modifier key function argument */
...
{ MODKEY, XK_i, incnmaster, {.i = +1 } },
{ MODKEY, XK_d, incnmaster, {.i = -1 } },
...
};
Key is a structure inside dwm.c source file (www):
typedef struct {
unsigned int mod;
KeySym keysym;
void (*func)(const Arg *);
const Arg arg;
} Key;
Function incnmaster() is defined in dwm.c source file (www):
void
incnmaster(const Arg *arg)
{
selmon->nmaster = MAX(selmon->nmaster + arg->i, 0);
arrange(selmon);
}
where arg is a pointer to Arg (Arg*) which is a union (I don't quite understand how to deal with the argument .i = +1):
typedef union {
int i;
unsigned int ui;
float f;
const void *v;
} Arg;
selmon is a structure Monitor:
struct Monitor {
char ltsymbol[16];
float mfact;
int nmaster;
int num;
int by; /* bar geometry */
int mx, my, mw, mh; /* screen size */
int wx, wy, ww, wh; /* window area */
unsigned int seltags;
unsigned int sellt;
unsigned int tagset[2];
int showbar;
int topbar;
Client *clients;
Client *sel;
Client *stack;
Monitor *next;
Window barwin;
const Layout *lt[2];
};
MAX is defined in a separate source file util.h (www) as:
#define MAX(A, B) ((A) > (B) ? (A) : (B))
and function arrange() is defined like this:
void
arrange(Monitor *m)
{
if (m)
showhide(m->stack);
else for (m = mons; m; m = m->next)
showhide(m->stack);
if (m) {
arrangemon(m);
restack(m);
} else for (m = mons; m; m = m->next)
arrangemon(m);
}
I don't think I have to dig any further...
Now I think I need to implement some sort of an if sentantce in the C code to prevent selmon->nmaster to increase too much, but I am a bit confused. Can anyone help?
Why are you holding number of clients when it's linked list? You cant obtain number of clients on demand. Similar code can be found monocle count patch. If you really have to keep that count yourself (for performance reasons), I would look at any place where is Client list modified by dwm and project that modification to counter.
Structure Client contains pointer to a "next" Client, implementation may depend on whenever you want to use multihead support, but using code similar to Client* c = nexttiled(c->next), where first reference can be obtained from Monitor by calling Client* c = nexttiled(monitor->clients). It you count these in loop that should be enough.
If you want to still keep count yourself, I would find functions within dwm.c working with Client (detach, attach, ...) and find which are modifying list where you can increment/decrement counter based on executed operation.
Nobody answered before I could figure this one out myself. Problem is that Suckless team never implemented any kind of mechanism to count a number of opened windows (they call them clients). This is why I added a int nclients; member to struct Monitor:
struct Monitor {
char ltsymbol[16];
float mfact;
int nmaster;
int nclients;
int num;
int by; /* bar geometry */
int mx, my, mw, mh; /* screen size */
int wx, wy, ww, wh; /* window area */
unsigned int seltags;
unsigned int sellt;
unsigned int tagset[2];
int showbar;
int topbar;
Client *clients;
Client *sel;
Client *stack;
Monitor *next;
Window barwin;
const Layout *lt[2];
};
And then I made sure it is initialized to 0 at boot time by adding m->nclients = 0; in createmon() function which I guessed is ran at the beginning:
Monitor *
createmon(void)
{
Monitor *m;
m = ecalloc(1, sizeof(Monitor));
m->tagset[0] = m->tagset[1] = 1;
m->mfact = mfact;
m->nmaster = nmaster;
m->nclients = 0;
m->showbar = showbar;
m->topbar = topbar;
m->gappx = gappx;
m->lt[0] = &layouts[0];
m->lt[1] = &layouts[1 % LENGTH(layouts)];
strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol);
return m;
}
Then I made sure that my counter nclients is increased when new window appears. I added ++selmon->nclients; and arrange(selmon); (to be able to move clients to stack/master imediately after you close one of them) statement at the beginning of the spawn() function:
void
spawn(const Arg *arg)
{
++selmon->nclients;
arrange(selmon);
if (arg->v == dmenucmd)
dmenumon[0] = '0' + selmon->num;
if (fork() == 0) {
if (dpy)
close(ConnectionNumber(dpy));
setsid();
execvp(((char **)arg->v)[0], (char **)arg->v);
fprintf(stderr, "dwm: execvp %s", ((char **)arg->v)[0]);
perror(" failed");
exit(EXIT_SUCCESS);
}
}
Counter should be decreased when window is closed. This is why I added a --selmon->nclients; and arrange(selmon); (to be able to move clients to stack/master imediately after you close one of them) at the top of the killclient() function:
void
killclient(const Arg *arg)
{
--selmon->nclients;
arrange(selmon);
if (!selmon->sel)
return;
if (!sendevent(selmon->sel->win, wmatom[WMDelete], NoEventMask, wmatom[WMDelete], CurrentTime, 0 , 0, 0)) {
XGrabServer(dpy);
XSetErrorHandler(xerrordummy);
XSetCloseDownMode(dpy, DestroyAll);
XKillClient(dpy, selmon->sel->win);
XSync(dpy, False);
XSetErrorHandler(xerror);
XUngrabServer(dpy);
}
}
Now that the counter was set up, I could use it to rewrite incnmaster() function like this:
void
incnmaster(const Arg *arg)
{
if((arg->i > 0) && (selmon->nmaster < selmon->nclients)){
++selmon->nmaster;
}
if((arg->i < 0) && (selmon->nmaster > 0)){
--selmon->nmaster;
}
arrange(selmon);
}
Pay attention. My DWM is a bit patched, so some lines might be a bit
different than yours, but just stick to the same philosophy and you
can fix this.
This solution partialy works. It only fails to work when I:
A. use dmenu
dmenu when started can (a) open a client or (b) do nothing. In case (a) everything works as expected, but in case (b) nmaster and nclients become out of sync again.
So for example if I do scenario (b) once and use CTRL+i endless times, I will have to use CTRL+d once and nothing will happen, but if I use it once more one window is moved from master to stack area.
B. run any kind of windowed application from a terminal
It looks like DWM can't keep track of windows that are run from a terminal and treats them in a wrong way... In this case as well nmaster and nclients become out of sync.
Does anyone know if there is any other function besides spawn that is executed when any kind of window is opened?
This is still not solved!
I am creating a scrolling shooter for DMG using gbdk, it is based off some youtube tutorials and this example. In fact the link is the skeleton of my program. My issue is that the screen boundary conditions aren't working properly for down and right inputs. For up and left, they work correctly however, and the code for those is basically the exact same. I have also compiled the code from the link above, and it works correctly there. Apologies in advance, I have a childish sense of humor, so the game is penis-based.
The main differences between the skeleton code and mine is that I use a meta-sprite for the player, and an array for the x and y coordinates of the player. I have tried using individual integers for the locations and changing the bounds of the screen, but nothing seems to work.
#include <gb/gb.h>
#include <stdio.h>
#include "gameDicks.c"
#include "DickSprites.c"
#define SCREEN_WIDTH 160
BOOLEAN ishard = TRUE, playing = TRUE;
struct gameDicks flacid;
struct gameDicks hard;
INT8 spritesize = 8, dicklocation[2] = {20, 80};
int i;
void moveGameDicks(struct gameDicks* Dick, UINT8 x, UINT8 y){
move_sprite(Dick->spriteids[0], x, y);
move_sprite(Dick->spriteids[1], x + spritesize, y);
move_sprite(Dick->spriteids[2], x, y + spritesize);
move_sprite(Dick->spriteids[3], x + spritesize, y + spritesize);
}
void setuphard(INT8 dicklocation[2]){
hard.x = dicklocation[0];
hard.y = dicklocation[1];
hard.width = 16;
hard.height = 16;
//load sprites
set_sprite_tile(0,0);
hard.spriteids[0] = 0;
set_sprite_tile(1,1);
hard.spriteids[1] = 1;
set_sprite_tile(2,2);
hard.spriteids[2] = 2;
set_sprite_tile(3,3);
hard.spriteids[3] = 3;
}
void init_screen()
{
SHOW_BKG;
SHOW_SPRITES;
DISPLAY_ON;
}
void init_player()
{
SHOW_SPRITES;
set_sprite_data(0, 8, DickSprites);
setuphard(dicklocation);
}
void input()
{
if (joypad() & J_UP && dicklocation[1])
{
if (dicklocation[1] <= 16){
dicklocation[1] = 16;
}
else{
dicklocation[1]--;
}
}
if (joypad() & J_DOWN && dicklocation[1])
{
if (dicklocation[1] >= 150){
dicklocation[1] = 150;
}
else{
dicklocation[1]++;
}
}
}
void update_sprites()
{
moveGameDicks(&hard, dicklocation[0], dicklocation[1]);
}
int main()
{
init_screen();
init_player();
init_screen();
while(playing)
{
wait_vbl_done(2);
input();
update_sprites();
}
return 0;
}
What I expect is to be able to move the player up to y = 16, and down to y = 150. When it hits these values, it stops moving until you go the other direction. Instead, what I see happen is that the up direction works as expected, but as soon as the down key is pressed - no matter the y-location - the player is immediately sent to the bottom of the screen. From there, pressing up sends it to the very top. Further, the player can only move from the top position to the bottom, and not scroll in between. I'm baffled by this because the conditions are the exact same (except for the y-values), so I don't understand why they behave so differently.
Using an unsigned int may help here as an 8-bit integer will only hold values from -128 to 127, which might cause undefined behaviour when you compare it with over 150, pushing it to a negative value?
You have defined dicklocation as an INT8, when it would be better as a UINT8 or even longer if you plan on ever having a screen size larger than 255 bytes.
I am making a game in CSFML for the purpose of a school exercise
In order to fit all the requirement I must a game who follows the rule of a finite runne suc as geometry dash. It does everything except a major feature: get a map from a file that will be like:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXX2XXXXXXXXX2XXXXXXXXXXXXXXE
111111111111111111111111111111111111
X representing nothing (they will be a background that is displayed)
2 is spike
1 is the ground
E is the end, it will stop the program after displaying a victory screen
(each character will be replace by the texture who is assigned except X who represent empty space)
I had only access to few C functions ( write, free, malloc, rand, open, read,getline)
I was thinking about just reading the file and storing it as a char*, but the thing is I don't know how to make spikes appear on the screen one by one, when they must.
You need to choose a size for all of your blocks.
Each blocks (X, 2, 1, E) need to have the same size.
Example (with block 64*64px)
void display_map(char **map)
{
char *image = NULL;
int size_block = 64;
for (int i = 0; map[i] != NULL; i++) {
for (int j = 0; map[i][j] != '\0'; j++) {
switch (map[i][j]) {
case 'X':
image = "nothing";
break;
case '2':
image = "pike";
break;
// ....
}
display_at_position(i * size_block, j * size_block, image);
}
}
}
I work on embedded device's firmware (write in C), I need to take a screenshot from the display and save it as a bmp file. Currently I work on the module that generates bmp file data. The easiest way to do that is to write some function that takes the following arguments:
(for simplicity, only images with indexed colors are supported in my example)
color_depth
image size (width, height)
pointer to function to get palette color for color_index (i)
pointer to function to get color_index of the pixel with given coords (x, y)
pointer to function to write image data
And then user of this function should call it like that:
/*
* Assume we have the following functions:
* int_least32_t palette_color_get (int color_index);
* int pix_color_idx_get (int x, int y);
* void data_write (const char *p_data, size_t len);
*/
bmp_file_generate(
1, //-- color_depth
x, y, //-- size
palette_color_get,
pic_color_idx_get,
data_write
);
And that's it: this functions does all the job, and returns only when job is done (i.e. bmp file generated and "written" by given user callback function data_write().
BUT, I need to make bmp_writer module to be usable in cooperative RTOS, and data_write() might be a function that actually transmits data via some protocol (say, UART) to another device), so, this function needs to be called only from Task context. This approach doesn't work then, I need to make it in OO-style, and its usage should look like this:
/*
* create instance of bmp_writer with needed params
* (we don't need "data_write" pointer anymore)
*/
T_BmpWriter *p_bmp_writer = new_bmp_writer(
1, //-- color_depth
x, y, //-- size
palette_color_get,
pic_color_idx_get
);
/*
* Now, byte-by-byte get all the data!
*/
while (bmp_writer__data_available(p_bmp_writer) > 0){
char cur_char = bmp_writer__get_next_char(p_bmp_writer);
//-- do something useful with current byte (i.e. cur_char).
// maybe transmit to another device, or save to flash, or anything.
}
/*
* Done! Free memory now.
*/
delete_bmp_writer(p_bmp_writer);
As you see, user can call bmp_writer__get_next_char(p_bmp_writer) when he need that, and handle received data as he wants.
Actually I already implemented this, but, with that approach, all the algorithm becomes turned inside out, and this code is extremely non-readable.
I'll show you a part of old code that generates palette data (from the function that does all the job, and returns only when job is done), and appropriate part of new code (in state-machine style).
Old code:
void bmp_file_generate(/*....args....*/)
{
//-- ... write headers
//-- write palette (if needed)
if (palette_colors_cnt > 0){
size_t i;
int_least32_t cur_color;
for (i = 0; i < palette_colors_cnt; i++){
cur_color = callback_palette_color_get(i);
callback_data_write((const char *)&cur_color, sizeof(cur_color));
}
}
//-- ...... write image data ..........
}
As you see, very short and easy-readable code.
Now, new code.
It looks like state-machine, because it's actually splitted by stages (HEADER_WRITE, PALETTE_WRITE, IMG_DATA_WRITE), each stage has its own context. In the old code, context was saved in local variables, but now we need to make the structure and allocate it from heap.
So:
/*
* Palette stage context
*/
typedef struct {
size_t i;
size_t cur_color_idx;
int_least32_t cur_color;
} T_StageContext_Palette;
/*
* Function that switches stage.
* T_BmpWriter is an object context, and pointer *me is analogue of "this" in OO-languages.
* bool_start is 1 if stage is just started, and 0 if it is finished.
*/
static void _stage_start_end(T_BmpWriter *me, U08 bool_start)
{
switch (me->stage){
//-- ...........other stages.........
case BMP_WR_STAGE__PALETTE:
if (bool_start){
//-- palette stage is just started. Allocate stage context and initialize it.
me->p_stage_context = malloc(sizeof(T_StageContext_Palette));
memset(me->p_stage_context, 0x00, sizeof(T_StageContext_Palette));
//-- we need to get first color, so, set index of byte in cur_color to maximum
((T_StageContext_Palette *)me->p_stage_context)->i = sizeof(int_least32_t);
} else {
free(me->p_stage_context);
me->p_stage_context = NULL;
}
break;
//-- ...........other stages.........
}
}
/*
* Function that turns to the next stage
*/
static void _next_stage(T_BmpWriter *me)
{
_stage_start_end(me, 0);
me->stage++;
_stage_start_end(me, 1);
}
/*
* Function that actually does the job and returns next byte
*/
U08 bmp_writer__get_next_char(T_BmpWriter *me)
{
U08 ret = 0; //-- resulting byte to return
U08 bool_ready = 0; //-- flag if byte is ready
while (!bool_ready){
switch (me->stage){
//-- ...........other stages.........
case BMP_WR_STAGE__PALETTE:
{
T_StageContext_Palette *p_stage_context =
(T_StageContext_Palette *)me->p_stage_context;
if (p_stage_context->i < sizeof(int_least32_t)){
//-- return byte of cur_color
ret = *( (U08 *)&p_stage_context->cur_color + p_stage_context->i );
p_stage_context->i++;
bool_ready = 1;
} else {
//-- need to get next color (or even go to next stage)
if (p_stage_context->cur_color_idx < me->bmp_details.palette_colors_cnt){
//-- next color
p_stage_context->cur_color = me->callback.p_palette_color_get(
me->callback.user_data,
p_stage_context->cur_color_idx
);
p_stage_context->cur_color_idx++;
p_stage_context->i = 0;
} else {
//-- next stage!
_next_stage(me);
}
}
}
break;
//-- ...........other stages.........
}
}
return ret;
}
So huge code, and it's so hard to understand it!
But I really have no idea how to make it in some different way, to be able to get information byte-by-byte.
Does anyone know how to achieve this, and keep code readability?
Any help is appreciated.
You can try protothread, which is useful to transform a state-machine based program into thread-style program. I'm not 100% sure that it can solve your problem elegantly, you can give it a try. The paper is a good starting point: Protothreads: simplifying event-driven programming of memory-constrained embedded systems
Here is its source code: http://code.google.com/p/protothread/
By the way, protothread is also used in the Contiki embedded OS, for implementing process in Contiki.