implemented the tutorial

This commit is contained in:
sirlilpanda
2025-09-02 19:49:48 +12:00
parent 98d39992a0
commit a5846fa659

124
README.md
View File

@@ -87,16 +87,15 @@ classDiagram
+DeviceInterface_s methods
}
Device_t *-- DeviceState_t
%% DeviceInterface_s --o Device_t
Device_t o-- DeviceInterface_s
DeviceInterface_s --o DeviceState_t
```
(note not real uml diagram because no one knows how to read them)
1. so to implement this we will first start off with the [device struct](/src/device.h#L67)
1. so to implement this we will first start off with the [device struct](/src/device.h#L64)
```c
// device.h
typedef struct Device_s{
DeviceState_t state,
char* entered_string,
@@ -106,6 +105,7 @@ classDiagram
2. Next we will implement the [methods](/src/device.h#L34) which in this case will be an [interface](https://en.wikipedia.org/wiki/Interface_(object-oriented_programming)). These methods are all the changes that can be made to the device i.e. your [inputs](#inputs).
```c
// device.h
typedef struct DeviceInterface_s{
void (*pressPwr)(Device_t*);
void (*pressStrInput)(Device_t*);
@@ -116,54 +116,90 @@ classDiagram
3. and finally we will implement the [struct to hold the actual state](/src/device.h#L52) of the device. (note there is some funkiness in c with implementing this since you will need forward decls)
```c
// device.h
typedef struct DeviceState_s{
const char* state_name;
DeviceInterface_t methods;
}DeviceState_t;
```
4. now we need to implement the 3 functions for device ive implemented one here as the rest are very [similar](/src/device.c#L14). So this function takes the give device and then uses the methods defined within the
4. now we need to implement the 3 functions for device inputs, ive implemented one here as the rest are very [similar](/src/device.c#L14). So this function takes the given device and then uses the methods defined within the device state to change the current state of the device.
```c
void pressPwrButton(Device_t *device) {
device->state.methods.pressPwr(device);
}
// device.c
void pressPwrButton(Device_t *device) {
device->state.methods.pressPwr(device);
}
```
5. now we will create a new header file called state, this file will contain the functions that sets the state of the given device to a new state. so we will implement a function per out defined [states](#states).
```c
//state.h
void setDeviceStateToUnlock(Device_t *device);
void setDeviceStateToDebug(Device_t *device);
void setDeviceStateToLock(Device_t *device);
void setDeviceStateToOff(Device_t *device);
```
6. now we can implement one of these states as the rest of them should be fairly self explanatory. the state we will be implementing is the [lock state](/src/states/lock_state.c) as this should show off most of the different transitions. we will first start out with implementing the set state function. this function takes the given device and sets that devices state to this new state.
```c
// lock_state.h
void setDeviceStateToLock(Device_t *device) {
device->state = (DeviceState_t){
.state_name = lock_state_name,
.methods = (DeviceInterface_t){
.pressPwr = &pressPwrMethod,
.pressStrInput = &pressStrInputMethod,
.pressLock = &pressLockMethod,
},
};
}
```
7. now that we have the function for setting this state we can now implement the functions for what this functions does depending on the inputs we will first start with the power button function. This is one of the simplest when the power button is press i.e. when the function is called we set the devices state to off.
```c
// lock_state.c
static void pressPwrMethod(Device_t *device) {
printf("turning off device\n");
setDeviceStateToOff(device);
}
```
8. now we will implement the next function the lock button which in the lock state will do nothing.
```c
// lock_state.c
static void pressLockMethod(Device_t *device) {
(void) device;
printf("nothing happens\n");
}
```
9. finally we create the function for handling the text input, this function will check the entered string to see if its either "dbg", or "pwd" and if it is enter debug or unlock respectively.
```c
static void pressStrInputMethod(Device_t *device) {
if (strcmp(device->entered_string, "dbg") == 0) {
printf("entering debug state\n");
setDeviceStateToDebug(device);
return;
}
if (strcmp(device->entered_string, "pwd") == 0) {
printf("entering unlock state\n");
setDeviceStateToUnlock(device);
return;
}
printf("unknown string %s\n", device->entered_string);
}
```
10. now we just need to implement the other [states](#states) in the same way we implemented this first one.
11. finally we need to test our device. firstly within main we create a new device and set its initial state to off.
```c
Device_t device;
initDevice(&device);
setDeviceStateToOff(&device);
```
12. now we can test that the device correctly travels through each state, by pressing one of the buttons ([invoking the input method functions](/src/device.h#L34)) then checking that the `state_name` is equal to the set state name.
```c
// turn on the device
pressPwrButton(&device);
if (strcmp(device.state.state_name, "lock_state.c") != 0) return 0;
now to actually create the meat of this device, we need to implement the logic that changes the state. To do this we will first create a [header file](/src/states/state.h) to store all the functions that change the state of the device. now we will implement and state so we will start [off state](/src/states/off_state.c) with the easiest one the off state (no bother having the rest if the device cant turn on). to create this state we will implement the functions that are defined within the [interface](/src/device.h#L34) we created, this functions will define what happens to the current state based on the given input. so `pressPwrMethod` is what happens when the power button is pressed when its in the off state (to make the code [grep-able](https://morizbuesing.com/blog/greppability-code-metric/) these functions should probably be prefixed with the given state like `OffStatePressPwrMethod`). these function are prefix with the static keyword as they should only ever be used here and not exposed. the other methods should be self explanatory. lastly we must implement the function that actually changes the state of the device to this given state, so to change the state we just set the devices state to a new struct with the given methods that we defined within this file.
```c
static void pressPwrMethod(Device_t *device) {
printf("turning on device\n");
setDeviceStateToLock(device);
}
static void pressStrInputMethod(Device_t *device) {
// cast it to void since its unused
(void) device;
printf("nothing happens\n");
}
static void pressLockMethod(Device_t *device) {
// cast it to void since its unused
(void) device;
printf("nothing happens\n");
}
void setDeviceStateToOff(Device_t *device) {
device->state = (DeviceState_t){
.state_name = off_state_name,
.device = device,
.methods = (DeviceInterface_t){
.pressPwr = &pressPwrMethod,
.pressStrInput = &pressStrInputMethod,
.pressLock = &pressLockMethod,
},
};
}
```
and finally we expose the set method in our [header file](/src/states/state.h) so the other states can set to this state. next we just implement the rest of the state Lock, Unlock, and Debug and can finally test our creation in [`main.c`](/src/main.c).
```
now that you have implement this basic state pattern your tech lead comes to you and tells you to implement one more state then they will let you throw a brick at management as a treat this state is:
- A calling state where:
@@ -173,9 +209,7 @@ now that you have implement this basic state pattern your tech lead comes to you
so try implementing this yourself.
other good resources for learning how this patterns works is [bob nystrom gameprogrammingpatterns](https://gameprogrammingpatterns.com/state.html) as well as [refactoring Guru](https://refactoring.guru/design-patterns/state) however both of these implement this in the fun languages with object and interfaces.
other good resources for learning how this patterns works is [bob nystrom's game programming patterns book](https://gameprogrammingpatterns.com/state.html) as well as [refactoring Guru](https://refactoring.guru/design-patterns/state) however both of these implement this in the fun languages with object and interfaces.
# how to build and run it