Created
April 22, 2012 08:20
-
-
Save rsms/2462695 to your computer and use it in GitHub Desktop.
I/O Patch implementation in C
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// I/O Patch implementation in C | |
// | |
// A patch has strongly defined inputs and outputs, which can be connected to other | |
// patches' outputs and inputs to form program flow. A patch implements user code | |
// which reads zero or more immutable input values and mutates zero or more output | |
// values. This user code is implemented as conceptually pure functions (although | |
// the user is able to break pureness). | |
// | |
// Build and run: | |
// cc -o iopatch iopatch.c && ./iopatch | |
// | |
// | |
// Copyright (c) 2012 Rasmus Andersson <http://rsms.me/> | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
#include <stdlib.h> | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <memory.h> | |
#include <assert.h> | |
#include <unistd.h> | |
// ------------------------------------------------------------------------------- | |
// Library | |
typedef void(*PatchFunction)(void*); | |
struct Patch; | |
typedef struct Connection { | |
struct Patch *inputState; | |
void *inputPtr; | |
} Connection; | |
typedef struct Connections { | |
size_t capacity; | |
size_t count; | |
Connection **connections; | |
} Connections; | |
typedef struct Patch { | |
PatchFunction func; | |
Connections *connections; | |
} Patch; | |
void _Connect(Patch *outputState, void *outputPtr, | |
Patch *inputState, void *inputPtr ) { | |
printf("Connect: %p:%zx -> %p:%zx\n", | |
outputState, ((size_t)outputPtr)-((size_t)outputState), | |
inputState, ((size_t)inputPtr)-((size_t)inputState)); | |
Connection *connection = (Connection*)malloc(sizeof(Connection)); | |
connection->inputState = inputState; | |
connection->inputPtr = inputPtr; | |
Connections *connections = outputState->connections; | |
if (connections == NULL) { | |
outputState->connections = connections = (Connections*)malloc(sizeof(Connections)); | |
connections->capacity = 1; | |
connections->count = 1; | |
connections->connections = (Connection**)malloc(sizeof(void*)*connections->capacity); | |
connections->connections[0] = connection; | |
} else { | |
if (connections->capacity == connections->count) { | |
connections->capacity *= 2; | |
connections->connections = (Connection**)realloc(connections->connections, | |
sizeof(void*)*connections->capacity); | |
} | |
connections->connections[connections->count++] = connection; | |
} | |
} | |
// void Connect(Patch*, Symbol, Symbol, Patch*) | |
#define Connect(outPatch, outName, inName, inPatch) \ | |
_Connect((Patch*)(outPatch), (void*)&((outPatch)->output__##outName), \ | |
(Patch*)(inPatch), (void*)&((inPatch)->input__##inName) ) | |
void _TriggerOutput(Patch *outputState, void *outputPtr, size_t outputSize) { | |
if (!outputState->connections) { | |
printf("TriggerOutput: Dead end -- no connections on output %p:%zx).\n", | |
outputState, ((size_t)outputPtr)-((size_t)outputState)); | |
return; | |
} | |
Connections *connections = outputState->connections; | |
size_t i = 0, count = connections->count; | |
for (; i < count; ++i) { | |
//printf("TriggerOutput: Notify %zu\n", i); | |
Connection *receiver = connections->connections[i]; | |
// assign input value | |
memcpy(receiver->inputPtr, outputPtr, outputSize); | |
// run receivers main function | |
assert(receiver->inputState != NULL); | |
void *x = (void*)receiver->inputState; | |
x = (void*)receiver->inputState->func; | |
receiver->inputState->func(receiver->inputState); | |
} | |
} | |
// void TriggerOutput(Patch*, Symbol) | |
#define TriggerOutput(outPatch, outName) \ | |
_TriggerOutput((Patch*)(outPatch), (void*)&(outPatch)->output__##outName, \ | |
sizeof((outPatch)->output__##outName)); | |
Patch *_Create(size_t size, PatchFunction initFunc, PatchFunction runFunc) { | |
Patch *patch = (Patch*)calloc(1,size); | |
patch->func = runFunc; | |
if (initFunc) initFunc(patch); | |
return patch; | |
} | |
// Type* Create(Type) | |
#define Create(Type) (Type*)_Create(sizeof(Type), (PatchFunction)&Type##__init, \ | |
(PatchFunction)&Type##__run) | |
// ------------------------------------------------------------------------------- | |
// Patches | |
// | |
// Okay, here goes the basics: | |
// | |
// - A patch contains instance data (the struct) and a main function | |
// - Storage for any value is owned by an output of a patch instance | |
// - Inputs only reference data owned by some other output | |
// - Patch main functions are called when any input of a patch changed | |
// - The patch main function is responsible for calling TriggerOutput if | |
// it alters any output value. | |
// - TriggerOutput causes any connected patches to have their main | |
// functions executed (since effectively their input values changed). | |
// | |
// A patch that outputs received text to stdout and adds a trailing linebreak | |
typedef struct ConsoleOutput { Patch _; | |
char *input__text; | |
} ConsoleOutput; | |
void ConsoleOutput__init(ConsoleOutput *self) {}; | |
void ConsoleOutput__run(ConsoleOutput *self) { | |
printf("ConsoleOutput@%p: ENTER\n", self); | |
if (isatty(1)) { | |
printf("\e[36;1m%s\e[0m\n", self->input__text); | |
} else { | |
printf("%s\n", self->input__text); | |
} | |
} | |
// A patch that converts a number to text | |
typedef struct NumberToText { Patch _; | |
double input__number; | |
char *output__text; | |
} NumberToText; | |
void NumberToText__init(NumberToText *self) {} | |
void NumberToText__run(NumberToText *self) { | |
printf("NumberToText@%p: ENTER\n", self); | |
// Allocate storage on first use | |
const size_t bufsize = 64; | |
if (self->output__text == NULL) | |
self->output__text = (char*)malloc(bufsize); | |
snprintf(self->output__text, bufsize, "%f", self->input__number); | |
printf("NumberToText: SET output String text = '%s'\n", self->output__text); | |
TriggerOutput(self, text); | |
} | |
// A patch which outputs the result from multiplying two input numbers | |
typedef struct Multiply { Patch _; | |
double input__value1; | |
double input__value2; | |
double output__result; | |
} Multiply; | |
void Multiply__init(Multiply *self) {} | |
void Multiply__run(Multiply *self) { | |
printf("Multiply@%p: ENTER\n", self); | |
self->output__result = self->input__value1 * self->input__value2; | |
printf("Multiply: SET output Number result = %f\n", self->output__result); | |
TriggerOutput(self, result); | |
} | |
// Compositing with subpatches | |
typedef struct CompositeExample { Patch _; | |
double input__number; | |
double output__number; | |
Multiply *multiply1; | |
Multiply *multiply2; | |
Multiply *multiply3; | |
} CompositeExample; | |
void CompositeExample__init(CompositeExample *self) { | |
// Create three subpatches | |
self->multiply1 = Create(Multiply); | |
self->multiply2 = Create(Multiply); | |
self->multiply3 = Create(Multiply); | |
// Connect the output from multiply1 to the two inputs of multiply2 | |
Connect(self->multiply1,result, value1,self->multiply2); | |
Connect(self->multiply1,result, value2,self->multiply2); | |
// Connect the output from multiply2 to the two inputs of multiply3 | |
Connect(self->multiply2,result, value1,self->multiply3); | |
Connect(self->multiply2,result, value2,self->multiply3); | |
} | |
void CompositeExample__run(CompositeExample *self) { | |
printf("CompositeExample@%p: ENTER\n", self); | |
// [hack] Assign input values and run the Multiply patch's function | |
self->multiply1->input__value1 = self->input__number; | |
self->multiply1->input__value2 = self->input__number; | |
Multiply__run(self->multiply1); // executes our subpatch pipe | |
// Copy the output value of multiply3 to our output number | |
self->output__number = self->multiply3->output__result; | |
printf("CompositeExample: SET output Number number = %f\n", self->output__number); | |
TriggerOutput(self, number); | |
} | |
// ------------------------------------------------------------------------------- | |
// Driver | |
int main(int argc, char **argv) { | |
// Create patch instances | |
Multiply *multiply = Create(Multiply); | |
NumberToText *numberToText = Create(NumberToText); | |
NumberToText *numberToText2 = Create(NumberToText); | |
ConsoleOutput *consoleOutput = Create(ConsoleOutput); | |
CompositeExample *composite = Create(CompositeExample); | |
// Connect patches | |
Connect(multiply,result, /* --> */ number,numberToText); | |
Connect(multiply,result, /* --> */ number,numberToText2); | |
Connect(numberToText,text, /* --> */ text,consoleOutput); | |
Connect(multiply,result, /* --> */ number,composite); | |
Connect(composite,number, /* --> */ number,numberToText); | |
// Set some initial values on the root patch and run its function | |
multiply->input__value1 = 4.0; | |
multiply->input__value2 = 6.2; | |
Multiply__run(multiply); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment