aboutsummaryrefslogtreecommitdiffstats
path: root/README.md
blob: a918cf3dc13aa8afc8d345348dfb8c47d85ad7f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# Micro Interactive C Framework (MIC)

MIC is a tiny (<300 cloc) framework to boostrap interactive C projects.
Application code can be dynamically reloaded upon compilation, allowing C to be
used as a pseudo-scripting language. This property can be very useful when
developing, but might not be always desirable for a final release, for this
reason, static compilation can be selected as a make target when needed. The
main limitation of hot code reload is that global state can't be stored in the
main app code, but the framework is designed so the state is passed around when
needed and is always accessible for the relevant functions.

This framework can be easily extended to work with multiple platforms. However,
hot code reload will only be available for platforms that support dynamic
linking. For the moment, only POSIX compliant platforms are supported but
Windows is a target for the near future.

Remember that MIC is only a base, you can (and should) modify anything in here
to adapt it to your particular project.

## Example usage

Compile and execute the program:

```
make && ./build/app
```

While the application is running, make changes to the `src/app.c`. For example,
change the `app_step` function from:

```
static inline bool
app_step(AppState *state, PlatformAPI platform) {
    (void)state; // Unused parameter.
    platform.log("STEP");
    platform.sleep(100000);
    return true;
}
```

To:

```
static inline bool
app_step(AppState *state, PlatformAPI platform) {
    (void)state; // Unused parameter.
    platform.log("Hello world!");
    platform.sleep(100000);
    return true;
}
```

In another terminal (or using your editor of choice), recompile the program:

```
make
```

The changes should take effect in the running application.

## Implementation details

This framework offers two APIs for managing system resources and executing
application logic. The `PlatformAPI` is described in `src/platform.h` and it's
tasked with manage the resources of the platform and/or operating system. For
example, opening/reading files, allocating memory, logging, etc. To support
a new platform it is sufficient to implement the functions described on the API
and make sure to select such platform at compile time. Additionally, the
following internal functions must be implemented for static and dynamic linking:

```
// Load the dynamic library and initialize application. Returns the success or
// failure of this operation.
static bool _app_init(AppAPI *api, AppState *state, PlatformAPI platform);

// This function reloads the application code from the dynamic library,
// returning `true` if the AppAPI is ready to be used.
static bool _app_reload();

// Cleanup resources before exit.
static void _app_destroy(AppAPI *api, AppState *state, PlatformAPI platform);
```

The `AppAPI`, described in `src/app.h` and handles app initialization and
destruction, it contains the behaviour in the event of hot code reloading as
well as the main step function. The `init` function typically will be tasked
with initial allocation of resources. The duty of `reload` and `unload` is
application dependent, for example, we might wish to re-compile the shader
programs or reload geometry/sprites when we make modifications to the main
application code. The `step` function is called in every iteration of the loop,
and is where the bulk of the logic will likely be.

The application state is stored in a unique `AppState` structure. MIC has been
architected so that the state and platform functions are available in the
previously mentioned `AppAPI` functions. `AppState` can contain whatever state
is needed and will be persistent when re-compiling. The only limitation is that
if the `AppState` structure changes, the application must be restarted, as the
stored state will become invalid. This can be circumvented by pre-allocating
some memory that can then be partitioned with a pool allocator to suit the
application needs. This design is also conducive for saving state data by simply
serializing the `AppState` structure.

## Credits

I initially discovered the hot code reloading trick when watching [Casey
Muratori's][casey] [Handmade Hero][handmade] series. It blew my mind! However,
the code he presented was focused solely on Windows and I never end up trying it
myself. Some years later I tried to find a way of making this work in Mac/Linux
when I came across [Chris Wellons'][skeeto] take on this problem with a clever
function pointer API in his [interactive c demo][interac-c-demo] page. This
framework uses a lot of the ideas he presents there, as you can clearly see if
you look at [his code][interac-c-demo-github].

[casey]: https://caseymuratori.com
[handmade]: https://handmadehero.org
[skeeto]: https://nullprogram.com
[interac-c-demo]: https://nullprogram.com/blog/2014/12/23/
[interac-c-demo-github]: https://github.com/skeeto/interactive-c-demo