Compare commits
7 Commits
5c905e6f77
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f06a5659c | |||
| de13eeb85a | |||
| 7c086f6b8e | |||
| c58e57b7c7 | |||
| 3b04fd45c7 | |||
| b88fd280ea | |||
| b732f67b06 |
22
TODO.md
22
TODO.md
@@ -1,15 +1,3 @@
|
|||||||
# To improve the API
|
|
||||||
|
|
||||||
- finish tmpfs (rework the whole thing)
|
|
||||||
|
|
||||||
# To re-evaluate
|
|
||||||
|
|
||||||
- Make the hashmap growing/shrinking to save on memory.
|
|
||||||
- Exposing the internal hashmap implementation.
|
|
||||||
- Having a copy of the context stored directly in requests instead of having to use getComputerContext, as it simplifies the APIs and allows them
|
|
||||||
to be made more portable.
|
|
||||||
- Exposing more internal functions that may be useful to the user to prevent pointless rewriting and duplicate machine code.
|
|
||||||
|
|
||||||
# Vanilla components needed
|
# Vanilla components needed
|
||||||
|
|
||||||
Not everything OC has (as a few of them are really MC-centered) but most of it.
|
Not everything OC has (as a few of them are really MC-centered) but most of it.
|
||||||
@@ -44,7 +32,7 @@ Not everything OC has (as a few of them are really MC-centered) but most of it.
|
|||||||
- `iron_noteblock` component
|
- `iron_noteblock` component
|
||||||
- `colorful_lamp` component
|
- `colorful_lamp` component
|
||||||
|
|
||||||
# To make it good (after API is stable)
|
# To make it good
|
||||||
|
|
||||||
- make more stuff const if it can be. Gotta help out the optimizer
|
- make more stuff const if it can be. Gotta help out the optimizer
|
||||||
- put a bunch of internal-only functions as fastcall
|
- put a bunch of internal-only functions as fastcall
|
||||||
@@ -226,3 +214,11 @@ TODO: interface
|
|||||||
## OLED/IPU
|
## OLED/IPU
|
||||||
|
|
||||||
TODO: interface
|
TODO: interface
|
||||||
|
|
||||||
|
# To re-evaluate
|
||||||
|
|
||||||
|
- Make the hashmap growing/shrinking to save on memory.
|
||||||
|
- Exposing the internal hashmap implementation.
|
||||||
|
- Having a copy of the context stored directly in requests instead of having to use getComputerContext, as it simplifies the APIs and allows them
|
||||||
|
to be made more portable.
|
||||||
|
- Exposing more internal functions that may be useful to the user to prevent pointless rewriting and duplicate machine code.
|
||||||
|
|||||||
11
build.zig
11
build.zig
@@ -17,10 +17,9 @@ fn addEngineSources(b: *std.Build, opts: LibBuildOpts) *std.Build.Module {
|
|||||||
.strip = if (opts.optimize == .Debug) false else true,
|
.strip = if (opts.optimize == .Debug) false else true,
|
||||||
.unwind_tables = if (opts.optimize == .Debug) null else .none,
|
.unwind_tables = if (opts.optimize == .Debug) null else .none,
|
||||||
.pic = true,
|
.pic = true,
|
||||||
.sanitize_c = if(strict) .full else null,
|
.sanitize_c = if (strict) .full else null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
dataMod.addCSourceFiles(.{
|
dataMod.addCSourceFiles(.{
|
||||||
.files = &[_][]const u8{
|
.files = &[_][]const u8{
|
||||||
"src/neonucleus.c",
|
"src/neonucleus.c",
|
||||||
@@ -62,7 +61,7 @@ fn compileRaylib(b: *std.Build, os: std.Target.Os.Tag, buildOpts: LibBuildOpts,
|
|||||||
|
|
||||||
// passing it breaks it for some reason?
|
// passing it breaks it for some reason?
|
||||||
// TODO: make it not break
|
// TODO: make it not break
|
||||||
const raylib = b.addSystemCommand(&.{ "zig", "build"});
|
const raylib = b.addSystemCommand(&.{ "zig", "build" });
|
||||||
raylib.setCwd(b.path("foreign/raylib/"));
|
raylib.setCwd(b.path("foreign/raylib/"));
|
||||||
raylib.stdio = .inherit;
|
raylib.stdio = .inherit;
|
||||||
|
|
||||||
@@ -89,11 +88,7 @@ fn compileTheRightLua(b: *std.Build, target: std.Build.ResolvedTarget, version:
|
|||||||
// its a static library because COFF is a pile of shit
|
// its a static library because COFF is a pile of shit
|
||||||
const c = b.addLibrary(.{
|
const c = b.addLibrary(.{
|
||||||
.name = "lua",
|
.name = "lua",
|
||||||
.root_module = b.addModule("luamod", .{
|
.root_module = b.addModule("luamod", .{ .link_libc = true, .optimize = .ReleaseFast, .target = target, .pic = true }),
|
||||||
.link_libc = true,
|
|
||||||
.optimize = .ReleaseFast,
|
|
||||||
.target = target,
|
|
||||||
}),
|
|
||||||
.linkage = .static,
|
.linkage = .static,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -136,9 +136,9 @@ local function realInvoke(address, method, ...)
|
|||||||
if computer.isIdle() then sysyield() end -- machine idle
|
if computer.isIdle() then sysyield() end -- machine idle
|
||||||
end
|
end
|
||||||
|
|
||||||
if os.getenv("NN_INVDBG") and component.type(address) == os.getenv("NN_INVDBG") then
|
if component.type(address) == os.getenv("NN_INVDBG") or string.find(os.getenv("NN_METDBG") or "", method, nil, true) then
|
||||||
print("invoked", address, method, ...)
|
print("invoked", address, method, ...)
|
||||||
print("got", table.unpack(t))
|
print(string.format("got %d values", #t), table.unpack(t))
|
||||||
end
|
end
|
||||||
|
|
||||||
for i=1,#t do t[i] = sandboxValue(t[i]) end
|
for i=1,#t do t[i] = sandboxValue(t[i]) end
|
||||||
|
|||||||
22
src/main.c
22
src/main.c
@@ -556,6 +556,25 @@ void ne_env(nn_EnvironmentRequest *req) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nn_Exit ne_inetBullshit(nn_InternetRequest *req) {
|
||||||
|
// welcome to hell
|
||||||
|
nn_Computer *C = req->computer;
|
||||||
|
|
||||||
|
// TODO: make this shi connect to the real internet
|
||||||
|
// preferrably use libcurl, openssl and a basic blocking socket layer
|
||||||
|
|
||||||
|
if(req->action == NN_INTERNET_CONNECT) {
|
||||||
|
req->connection->state = ne_inetBullshit;
|
||||||
|
return NN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req->action == NN_INTERNET_CLOSE) {
|
||||||
|
return NN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(C) nn_setError(C, "inet bullshit: not supported");
|
||||||
|
return NN_EBADCALL;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
const char *player = getenv("USER");
|
const char *player = getenv("USER");
|
||||||
@@ -610,6 +629,7 @@ int main(int argc, char **argv) {
|
|||||||
nn_Component *dataCard = nn_createDataCard(u, NULL, &nn_defaultDataCards[2], NULL, ne_dataBullshit);
|
nn_Component *dataCard = nn_createDataCard(u, NULL, &nn_defaultDataCards[2], NULL, ne_dataBullshit);
|
||||||
nn_Component *modem = nn_createModem(u, NULL, &nn_defaultWirelessModems[1], NULL, ne_modemBullshit);
|
nn_Component *modem = nn_createModem(u, NULL, &nn_defaultWirelessModems[1], NULL, ne_modemBullshit);
|
||||||
nn_Component *tunnel = nn_createTunnel(u, NULL, &nn_defaultTunnel, NULL, ne_tunnelBullshit);
|
nn_Component *tunnel = nn_createTunnel(u, NULL, &nn_defaultTunnel, NULL, ne_tunnelBullshit);
|
||||||
|
nn_Component *inet = nn_createInternet(u, NULL, &nn_defaultInternetCard, NULL, ne_inetBullshit);
|
||||||
|
|
||||||
char *eepromCode = (char *)minBIOS;
|
char *eepromCode = (char *)minBIOS;
|
||||||
size_t eepromSize = strlen(minBIOS);
|
size_t eepromSize = strlen(minBIOS);
|
||||||
@@ -755,6 +775,7 @@ int main(int argc, char **argv) {
|
|||||||
nn_mountComponent(c, modem, 7, false);
|
nn_mountComponent(c, modem, 7, false);
|
||||||
nn_mountComponent(c, tunnel, 8, false);
|
nn_mountComponent(c, tunnel, 8, false);
|
||||||
nn_mountComponent(c, debugfs, 9, false);
|
nn_mountComponent(c, debugfs, 9, false);
|
||||||
|
nn_mountComponent(c, inet, 10, false);
|
||||||
int ltx = 0, lty = 0;
|
int ltx = 0, lty = 0;
|
||||||
double scrollBuf = 0;
|
double scrollBuf = 0;
|
||||||
double tickTime = 0;
|
double tickTime = 0;
|
||||||
@@ -994,6 +1015,7 @@ cleanup:;
|
|||||||
nn_dropComponent(modem);
|
nn_dropComponent(modem);
|
||||||
nn_dropComponent(tunnel);
|
nn_dropComponent(tunnel);
|
||||||
nn_dropComponent(debugfs);
|
nn_dropComponent(debugfs);
|
||||||
|
nn_dropComponent(inet);
|
||||||
// rip the universe
|
// rip the universe
|
||||||
nn_destroyUniverse(u);
|
nn_destroyUniverse(u);
|
||||||
ncl_destroyGlyphCache(gc);
|
ncl_destroyGlyphCache(gc);
|
||||||
|
|||||||
@@ -1250,7 +1250,7 @@ bool ncl_tmpRemoveEnt(ncl_TmpFile *dir, ncl_TmpFile *ent) {
|
|||||||
ent->parent = NULL;
|
ent->parent = NULL;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
pIter = &ent->next;
|
pIter = &(*pIter)->next;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1328,6 +1328,7 @@ static nn_Exit ncl_tmpfsHandler(nn_FSRequest *req) {
|
|||||||
}
|
}
|
||||||
tmpfs->fds[fd].file->openHandles--;
|
tmpfs->fds[fd].file->openHandles--;
|
||||||
tmpfs->fds[fd].file = NULL;
|
tmpfs->fds[fd].file = NULL;
|
||||||
|
tmpfs->fds[fd].offset = 0;
|
||||||
nn_unlock(ctx, tmpfs->lock);
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
return NN_OK;
|
return NN_OK;
|
||||||
}
|
}
|
||||||
@@ -1373,6 +1374,12 @@ static nn_Exit ncl_tmpfsHandler(nn_FSRequest *req) {
|
|||||||
nn_unlock(ctx, tmpfs->lock);
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
return NN_OK;
|
return NN_OK;
|
||||||
}
|
}
|
||||||
|
if(req->action == NN_FS_ISRO) {
|
||||||
|
nn_lock(ctx, tmpfs->lock);
|
||||||
|
req->isReadonly = tmpfs->isReadonly;
|
||||||
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
|
return NN_OK;
|
||||||
|
}
|
||||||
if(req->action == NN_FS_OPEN) {
|
if(req->action == NN_FS_OPEN) {
|
||||||
nn_lock(ctx, tmpfs->lock);
|
nn_lock(ctx, tmpfs->lock);
|
||||||
tmpfs->usage++;
|
tmpfs->usage++;
|
||||||
@@ -1445,6 +1452,7 @@ static nn_Exit ncl_tmpfsHandler(nn_FSRequest *req) {
|
|||||||
tmpfs->fds[fd].file = f;
|
tmpfs->fds[fd].file = f;
|
||||||
tmpfs->fds[fd].mode = mode[0];
|
tmpfs->fds[fd].mode = mode[0];
|
||||||
tmpfs->fds[fd].offset = mode[0] == 'a' ? f->datalen : 0;
|
tmpfs->fds[fd].offset = mode[0] == 'a' ? f->datalen : 0;
|
||||||
|
req->fd = fd;
|
||||||
nn_unlock(ctx, tmpfs->lock);
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
return NN_OK;
|
return NN_OK;
|
||||||
}
|
}
|
||||||
@@ -1462,6 +1470,11 @@ static nn_Exit ncl_tmpfsHandler(nn_FSRequest *req) {
|
|||||||
return NN_EBADCALL;
|
return NN_EBADCALL;
|
||||||
}
|
}
|
||||||
ncl_TmpFildes *fildes = &tmpfs->fds[fd];
|
ncl_TmpFildes *fildes = &tmpfs->fds[fd];
|
||||||
|
if(fildes->mode == 'r') {
|
||||||
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
|
nn_setError(C, "bad file descriptor");
|
||||||
|
return NN_EBADCALL;
|
||||||
|
}
|
||||||
size_t capNeeded = fildes->offset + req->write.len;
|
size_t capNeeded = fildes->offset + req->write.len;
|
||||||
if(capNeeded > fildes->file->datalen) {
|
if(capNeeded > fildes->file->datalen) {
|
||||||
char *data = nn_realloc(ctx, fildes->file->data, fildes->file->datalen, capNeeded);
|
char *data = nn_realloc(ctx, fildes->file->data, fildes->file->datalen, capNeeded);
|
||||||
@@ -1472,7 +1485,8 @@ static nn_Exit ncl_tmpfsHandler(nn_FSRequest *req) {
|
|||||||
fildes->file->data = data;
|
fildes->file->data = data;
|
||||||
fildes->file->datalen = capNeeded;
|
fildes->file->datalen = capNeeded;
|
||||||
}
|
}
|
||||||
memcpy(fildes->file->data + fildes->offset, req->write.buf, req->write.len);
|
// ubsan is acting weird
|
||||||
|
if(fildes->file->data != NULL) memcpy(fildes->file->data + fildes->offset, req->write.buf, req->write.len);
|
||||||
fildes->offset += req->write.len;
|
fildes->offset += req->write.len;
|
||||||
nn_unlock(ctx, tmpfs->lock);
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
return NN_OK;
|
return NN_OK;
|
||||||
@@ -1491,6 +1505,11 @@ static nn_Exit ncl_tmpfsHandler(nn_FSRequest *req) {
|
|||||||
return NN_EBADCALL;
|
return NN_EBADCALL;
|
||||||
}
|
}
|
||||||
ncl_TmpFildes *fildes = &tmpfs->fds[fd];
|
ncl_TmpFildes *fildes = &tmpfs->fds[fd];
|
||||||
|
if(fildes->mode != 'r') {
|
||||||
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
|
nn_setError(C, "bad file descriptor");
|
||||||
|
return NN_EBADCALL;
|
||||||
|
}
|
||||||
size_t size = fildes->file->datalen;
|
size_t size = fildes->file->datalen;
|
||||||
if(fildes->offset >= size) {
|
if(fildes->offset >= size) {
|
||||||
req->read.buf = NULL;
|
req->read.buf = NULL;
|
||||||
@@ -1501,6 +1520,7 @@ static nn_Exit ncl_tmpfsHandler(nn_FSRequest *req) {
|
|||||||
req->read.len = read;
|
req->read.len = read;
|
||||||
fildes->offset += read;
|
fildes->offset += read;
|
||||||
}
|
}
|
||||||
|
tmpfs->spaceUsed = 0;
|
||||||
nn_unlock(ctx, tmpfs->lock);
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
return NN_OK;
|
return NN_OK;
|
||||||
}
|
}
|
||||||
@@ -1518,6 +1538,11 @@ static nn_Exit ncl_tmpfsHandler(nn_FSRequest *req) {
|
|||||||
return NN_EBADCALL;
|
return NN_EBADCALL;
|
||||||
}
|
}
|
||||||
ncl_TmpFildes *fildes = &tmpfs->fds[fd];
|
ncl_TmpFildes *fildes = &tmpfs->fds[fd];
|
||||||
|
if(fildes->mode == 'a') {
|
||||||
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
|
nn_setError(C, "bad file descriptor");
|
||||||
|
return NN_EBADCALL;
|
||||||
|
}
|
||||||
int cur = fildes->offset;
|
int cur = fildes->offset;
|
||||||
int off = req->seek.off;
|
int off = req->seek.off;
|
||||||
size_t size = fildes->file->datalen;
|
size_t size = fildes->file->datalen;
|
||||||
@@ -1578,7 +1603,59 @@ static nn_Exit ncl_tmpfsHandler(nn_FSRequest *req) {
|
|||||||
}
|
}
|
||||||
tmpfs->spaceUsed -= ncl_tmpSpaceUsedIn(tmpfs, ripBro);
|
tmpfs->spaceUsed -= ncl_tmpSpaceUsedIn(tmpfs, ripBro);
|
||||||
ncl_tmpFreeFile(ctx, ripBro);
|
ncl_tmpFreeFile(ctx, ripBro);
|
||||||
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
|
return NN_OK;
|
||||||
}
|
}
|
||||||
|
// we gotta actually rename shit
|
||||||
|
if(ncl_isIllegalCopy(req->rename.from, req->rename.to)) {
|
||||||
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
|
nn_setError(C, "illegal copy operation");
|
||||||
|
return NN_EBADCALL;
|
||||||
|
}
|
||||||
|
char destParent[NN_MAX_PATH], destName[NN_MAX_PATH];
|
||||||
|
ncl_splitParentName(req->rename.to, destParent, destName);
|
||||||
|
ncl_TmpFile *src = ncl_tmpGet(tmpfs->root, req->rename.from);
|
||||||
|
if(src == NULL) {
|
||||||
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
|
nn_setError(C, "no such directory");
|
||||||
|
return NN_EBADCALL;
|
||||||
|
}
|
||||||
|
ncl_TmpFile *destDir = ncl_tmpGet(tmpfs->root, destParent);
|
||||||
|
if(destDir == NULL) {
|
||||||
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
|
nn_setError(C, "no such directory");
|
||||||
|
return NN_EBADCALL;
|
||||||
|
}
|
||||||
|
if(destDir->isFile) {
|
||||||
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
|
nn_setError(C, "not a directory");
|
||||||
|
return NN_EBADCALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // remove existing dest
|
||||||
|
ncl_TmpFile *existing = ncl_tmpGet(destDir, destName);
|
||||||
|
if(existing != NULL) {
|
||||||
|
if(!ncl_tmpCanRemove(existing)) {
|
||||||
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
|
nn_setError(C, "resource busy");
|
||||||
|
return NN_EBADCALL;
|
||||||
|
}
|
||||||
|
ncl_tmpRemoveEnt(destDir, existing);
|
||||||
|
tmpfs->spaceUsed -= ncl_tmpSpaceUsedIn(tmpfs, existing);
|
||||||
|
ncl_tmpFreeFile(ctx, existing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *newName = nn_strdup(ctx, destName);
|
||||||
|
if(newName == NULL) return NN_ENOMEM;
|
||||||
|
|
||||||
|
// transfer shi over
|
||||||
|
ncl_tmpRemoveEnt(src->parent, src);
|
||||||
|
src->next = destDir->files;
|
||||||
|
destDir->files = src;
|
||||||
|
nn_strfree(ctx, src->name);
|
||||||
|
src->name = newName;
|
||||||
|
|
||||||
nn_unlock(ctx, tmpfs->lock);
|
nn_unlock(ctx, tmpfs->lock);
|
||||||
return NN_OK;
|
return NN_OK;
|
||||||
}
|
}
|
||||||
@@ -3470,7 +3547,6 @@ void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat) {
|
|||||||
memcpy(stat->label, fs->label, stat->labellen);
|
memcpy(stat->label, fs->label, stat->labellen);
|
||||||
stat->fs.spaceUsed = ncl_fsGetUsage(fs);
|
stat->fs.spaceUsed = ncl_fsGetUsage(fs);
|
||||||
stat->fs.realDiskUsage = ncl_fsGetRealUsage(fs);
|
stat->fs.realDiskUsage = ncl_fsGetRealUsage(fs);
|
||||||
stat->fs.conf = &fs->conf;
|
|
||||||
stat->fs.path = fs->path;
|
stat->fs.path = fs->path;
|
||||||
stat->fs.filesOpen = 0;
|
stat->fs.filesOpen = 0;
|
||||||
for(size_t i = 0; i < NN_MAX_OPENFILES; i++) {
|
for(size_t i = 0; i < NN_MAX_OPENFILES; i++) {
|
||||||
@@ -3487,7 +3563,6 @@ void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat) {
|
|||||||
stat->labellen = drv->labellen;
|
stat->labellen = drv->labellen;
|
||||||
memcpy(stat->label, drv->label, stat->labellen);
|
memcpy(stat->label, drv->label, stat->labellen);
|
||||||
stat->drive.lastSector = drv->lastSector;
|
stat->drive.lastSector = drv->lastSector;
|
||||||
stat->drive.conf = &drv->conf;
|
|
||||||
nn_unlock(drv->ctx, drv->lock);
|
nn_unlock(drv->ctx, drv->lock);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3504,7 +3579,6 @@ void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat) {
|
|||||||
size_t sectorCount = drv->conf.capacity / drv->conf.sectorSize;
|
size_t sectorCount = drv->conf.capacity / drv->conf.sectorSize;
|
||||||
if(maxWrite > 0 && sectorCount > 0) wearlevel = drv->writeCount * 100.0 / sectorCount / maxWrite;
|
if(maxWrite > 0 && sectorCount > 0) wearlevel = drv->writeCount * 100.0 / sectorCount / maxWrite;
|
||||||
stat->flash.wearlevel = wearlevel;
|
stat->flash.wearlevel = wearlevel;
|
||||||
stat->flash.conf = &drv->conf;
|
|
||||||
nn_unlock(drv->ctx, drv->lock);
|
nn_unlock(drv->ctx, drv->lock);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3515,7 +3589,6 @@ void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat) {
|
|||||||
stat->usageCounter = ee->usage;
|
stat->usageCounter = ee->usage;
|
||||||
stat->labellen = ee->labellen;
|
stat->labellen = ee->labellen;
|
||||||
memcpy(stat->label, ee->label, stat->labellen);
|
memcpy(stat->label, ee->label, stat->labellen);
|
||||||
stat->eeprom.conf = &ee->conf;
|
|
||||||
stat->eeprom.codeUsed = ee->codelen;
|
stat->eeprom.codeUsed = ee->codelen;
|
||||||
stat->eeprom.dataUsed = ee->datalen;
|
stat->eeprom.dataUsed = ee->datalen;
|
||||||
nn_unlock(ee->ctx, ee->lock);
|
nn_unlock(ee->ctx, ee->lock);
|
||||||
@@ -3525,7 +3598,6 @@ void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat) {
|
|||||||
ncl_ScreenState *screen = state;
|
ncl_ScreenState *screen = state;
|
||||||
nn_lock(screen->ctx, screen->lock);
|
nn_lock(screen->ctx, screen->lock);
|
||||||
stat->usageCounter = screen->usage;
|
stat->usageCounter = screen->usage;
|
||||||
stat->screen.conf = &screen->conf;
|
|
||||||
stat->screen.depth = screen->depth;
|
stat->screen.depth = screen->depth;
|
||||||
stat->screen.flags = screen->flags;
|
stat->screen.flags = screen->flags;
|
||||||
stat->screen.keyboardCount = screen->keyboardCount;
|
stat->screen.keyboardCount = screen->keyboardCount;
|
||||||
|
|||||||
@@ -258,35 +258,29 @@ typedef struct ncl_ComponentStat {
|
|||||||
// specific properties
|
// specific properties
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
const nn_EEPROM *conf;
|
|
||||||
size_t codeUsed;
|
size_t codeUsed;
|
||||||
size_t dataUsed;
|
size_t dataUsed;
|
||||||
} eeprom;
|
} eeprom;
|
||||||
struct {
|
struct {
|
||||||
const nn_Filesystem *conf;
|
|
||||||
size_t spaceUsed;
|
size_t spaceUsed;
|
||||||
size_t realDiskUsage;
|
size_t realDiskUsage;
|
||||||
size_t filesOpen;
|
size_t filesOpen;
|
||||||
const char *path;
|
const char *path;
|
||||||
} fs;
|
} fs;
|
||||||
struct {
|
struct {
|
||||||
const nn_Drive *conf;
|
|
||||||
size_t lastSector;
|
size_t lastSector;
|
||||||
} drive;
|
} drive;
|
||||||
struct {
|
struct {
|
||||||
const nn_NandFlash *conf;
|
|
||||||
size_t currentWriteCount;
|
size_t currentWriteCount;
|
||||||
double wearlevel;
|
double wearlevel;
|
||||||
} flash;
|
} flash;
|
||||||
struct {
|
struct {
|
||||||
const nn_GPU *conf;
|
|
||||||
size_t vramFree;
|
size_t vramFree;
|
||||||
size_t bufferCount;
|
size_t bufferCount;
|
||||||
// can be NULL if there is none
|
// can be NULL if there is none
|
||||||
const char *boundScreen;
|
const char *boundScreen;
|
||||||
} gpu;
|
} gpu;
|
||||||
struct {
|
struct {
|
||||||
const nn_ScreenConfig *conf;
|
|
||||||
ncl_ScreenState *state;
|
ncl_ScreenState *state;
|
||||||
ncl_ScreenFlags flags;
|
ncl_ScreenFlags flags;
|
||||||
int viewportWidth;
|
int viewportWidth;
|
||||||
|
|||||||
271
src/neonucleus.c
271
src/neonucleus.c
@@ -3047,6 +3047,7 @@ const nn_Filesystem nn_defaultTmpFS = NN_INIT(nn_Filesystem) {
|
|||||||
.readsPerTick = 15,
|
.readsPerTick = 15,
|
||||||
.writesPerTick = 6,
|
.writesPerTick = 6,
|
||||||
.dataEnergyCost = 0.1 / NN_MiB,
|
.dataEnergyCost = 0.1 / NN_MiB,
|
||||||
|
.maxReadSize = 2048,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nn_Drive nn_defaultDrives[4] = {
|
const nn_Drive nn_defaultDrives[4] = {
|
||||||
@@ -4498,6 +4499,7 @@ static nn_Exit nn_fsHandler(nn_ComponentRequest *req) {
|
|||||||
nn_Context *ctx = req->ctx;
|
nn_Context *ctx = req->ctx;
|
||||||
nn_FSState *state = req->classState;
|
nn_FSState *state = req->classState;
|
||||||
nn_FSRequest freq;
|
nn_FSRequest freq;
|
||||||
|
freq.action = NN_FS_DROP;
|
||||||
freq.ctx = req->ctx;
|
freq.ctx = req->ctx;
|
||||||
freq.computer = req->computer;
|
freq.computer = req->computer;
|
||||||
freq.state = req->state;
|
freq.state = req->state;
|
||||||
@@ -7428,5 +7430,274 @@ nn_Component *nn_createTunnel(nn_Universe *universe, const char *address, const
|
|||||||
|
|
||||||
nn_InternetCard nn_defaultInternetCard = {
|
nn_InternetCard nn_defaultInternetCard = {
|
||||||
.protocolsSupported = NN_INET_ALL,
|
.protocolsSupported = NN_INET_ALL,
|
||||||
|
.maxReadSize = 64 * NN_KiB,
|
||||||
.transmissionEnergyCost = 0,
|
.transmissionEnergyCost = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef enum nn_InternetNum {
|
||||||
|
NN_INETNUM_MAXREADSIZE,
|
||||||
|
|
||||||
|
// is*Enabled
|
||||||
|
NN_INETNUM_ISHTTP,
|
||||||
|
NN_INETNUM_ISTCP,
|
||||||
|
NN_INETNUM_ISTLS,
|
||||||
|
NN_INETNUM_ISUDP,
|
||||||
|
NN_INETNUM_ISWEBSOCK,
|
||||||
|
|
||||||
|
// TCP/TLS connection
|
||||||
|
NN_INETNUM_CONNECT,
|
||||||
|
// WebSocket/UDP connection
|
||||||
|
NN_INETNUM_CHANNEL,
|
||||||
|
// HTTP/HTTPS request
|
||||||
|
NN_INETNUM_REQUEST,
|
||||||
|
|
||||||
|
NN_INETNUM_COUNT,
|
||||||
|
} nn_InternetNum;
|
||||||
|
|
||||||
|
typedef struct nn_InternetState {
|
||||||
|
nn_Context *ctx;
|
||||||
|
nn_InternetCard inet;
|
||||||
|
nn_InternetHandler *handler;
|
||||||
|
} nn_InternetState;
|
||||||
|
|
||||||
|
static nn_Exit nn_inetCloseConn(nn_InternetRequest req, nn_InternetHandler *handler, nn_InternetConnection *conn) {
|
||||||
|
if(conn == NULL) return NN_OK;
|
||||||
|
if(conn->state != NULL) {
|
||||||
|
req.connection = conn;
|
||||||
|
req.action = NN_INTERNET_CLOSE;
|
||||||
|
nn_Exit e = handler(&req);
|
||||||
|
if(e) return e;
|
||||||
|
}
|
||||||
|
conn->state = NULL;
|
||||||
|
return NN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pushes a userdata that makes the connection, to save on brain power.
|
||||||
|
static nn_Exit nn_inetMakeConn(nn_InternetRequest req, nn_InternetHandler *handler, nn_InternetProtocol proto) {
|
||||||
|
if(req.action != NN_INTERNET_CONNECT) return NN_EBADSTATE;
|
||||||
|
nn_Context *ctx = req.ctx;
|
||||||
|
|
||||||
|
nn_InternetConnection *conn = NULL;
|
||||||
|
nn_Exit e = NN_OK;
|
||||||
|
|
||||||
|
conn = nn_alloc(ctx, sizeof(*conn));
|
||||||
|
if(conn == NULL) {
|
||||||
|
e = NN_ENOMEM;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn->protocol = proto;
|
||||||
|
conn->port = req.connect.port;
|
||||||
|
conn->state = NULL;
|
||||||
|
nn_randomUUID(ctx, conn->id);
|
||||||
|
|
||||||
|
req.connection = conn;
|
||||||
|
e = handler(&req);
|
||||||
|
if(e) goto fail;
|
||||||
|
|
||||||
|
int idx = nn_allocUserdata(req.computer, conn, req.localAddress);
|
||||||
|
if(idx < 0) {
|
||||||
|
e = NN_ELIMIT;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
e = nn_pushuserdata(req.computer, idx);
|
||||||
|
if(e) goto fail;
|
||||||
|
|
||||||
|
return NN_OK;
|
||||||
|
fail:
|
||||||
|
nn_inetCloseConn(req, handler, conn);
|
||||||
|
nn_free(ctx, conn, sizeof(*conn));
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
static nn_Exit nn_inetHandler(nn_ComponentRequest *req) {
|
||||||
|
nn_Context *ctx = req->ctx;
|
||||||
|
nn_InternetState *state = req->classState;
|
||||||
|
nn_Computer *C = req->computer;
|
||||||
|
nn_Exit e;
|
||||||
|
|
||||||
|
if(req->action == NN_COMP_CHECKMETHOD) return NN_OK;
|
||||||
|
|
||||||
|
nn_InternetRequest ireq;
|
||||||
|
ireq.ctx = ctx;
|
||||||
|
ireq.computer = C;
|
||||||
|
ireq.state = req->state;
|
||||||
|
ireq.inet = &state->inet;
|
||||||
|
ireq.localAddress = req->compAddress;
|
||||||
|
ireq.connection = NULL;
|
||||||
|
|
||||||
|
if(req->action == NN_COMP_DROP) {
|
||||||
|
ireq.action = NN_INTERNET_DROP;
|
||||||
|
state->handler(&ireq);
|
||||||
|
nn_free(ctx, state, sizeof(*state));
|
||||||
|
return NN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req->action == NN_COMP_USERDATA) {
|
||||||
|
nn_UserdataRequest *ureq = req->user;
|
||||||
|
nn_InternetConnection *conn = ureq->state;
|
||||||
|
|
||||||
|
if(conn->state == NULL) return NN_EBADSTATE;
|
||||||
|
|
||||||
|
if(ureq->action == NN_USER_DROP) {
|
||||||
|
nn_inetCloseConn(ireq, state->handler, conn);
|
||||||
|
nn_free(ctx, conn, sizeof(*conn));
|
||||||
|
return NN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ureq->action == NN_USER_GETMETHOD) {
|
||||||
|
size_t idx = ureq->getmethod.idx;
|
||||||
|
nn_Method *method = ureq->getmethod.method;
|
||||||
|
|
||||||
|
if(idx == 0) {
|
||||||
|
method->name = "finishConnect";
|
||||||
|
method->doc = "function(): boolean - Returns whether the connection finished. Can also return an error if the connection failed, or no error if still pending";
|
||||||
|
method->flags = NN_DIRECT;
|
||||||
|
} else if(idx == 1) {
|
||||||
|
method->name = "id";
|
||||||
|
method->doc = "function(): string - Returns the id of the connection";
|
||||||
|
method->flags = NN_DIRECT;
|
||||||
|
} else if(idx == 2) {
|
||||||
|
method->name = "close";
|
||||||
|
method->doc = "function() - Closes the connection";
|
||||||
|
method->flags = NN_DIRECT;
|
||||||
|
} else if(idx == 3) {
|
||||||
|
method->name = "read";
|
||||||
|
method->doc = "function(n?: number): string? - Reads data from the connection. Returns the data, or nil if EoF";
|
||||||
|
method->flags = NN_DIRECT;
|
||||||
|
} else if(idx == 4) {
|
||||||
|
method->name = conn->protocol == NN_INET_HTTP ? NULL : "write";
|
||||||
|
method->doc = "function(data: string): integer - Writes data to the connection. Returns how much data was successfully written";
|
||||||
|
method->flags = NN_DIRECT;
|
||||||
|
} else {
|
||||||
|
ureq->getmethod.method = NULL;
|
||||||
|
}
|
||||||
|
return NN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ureq->action == NN_USER_INVOKE) {
|
||||||
|
const char *method = ureq->invoke.method;
|
||||||
|
|
||||||
|
if(nn_strcmp(method, "id") == 0) {
|
||||||
|
ureq->invoke.returnCount = 1;
|
||||||
|
return nn_pushlstring(C, conn->id, 36);
|
||||||
|
}
|
||||||
|
if(nn_strcmp(method, "close") == 0) {
|
||||||
|
e = nn_inetCloseConn(ireq, state->handler, conn);
|
||||||
|
if(e) return e;
|
||||||
|
ureq->invoke.returnCount = 1;
|
||||||
|
return nn_pushbool(C, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(C) nn_setError(C, "internet: not implemented yet");
|
||||||
|
return NN_EBADCALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
nn_InternetNum method = req->methodIdx;
|
||||||
|
|
||||||
|
if(method == NN_INETNUM_MAXREADSIZE) {
|
||||||
|
req->returnCount = 1;
|
||||||
|
return nn_pushinteger(C, state->inet.maxReadSize);
|
||||||
|
}
|
||||||
|
if(method == NN_INETNUM_ISHTTP) {
|
||||||
|
req->returnCount = 1;
|
||||||
|
return nn_pushbool(C, (state->inet.protocolsSupported & NN_INET_HTTP) != 0);
|
||||||
|
}
|
||||||
|
if(method == NN_INETNUM_ISTCP) {
|
||||||
|
req->returnCount = 1;
|
||||||
|
return nn_pushbool(C, (state->inet.protocolsSupported & NN_INET_TCP) != 0);
|
||||||
|
}
|
||||||
|
if(method == NN_INETNUM_ISTLS) {
|
||||||
|
req->returnCount = 1;
|
||||||
|
return nn_pushbool(C, (state->inet.protocolsSupported & NN_INET_TLS) != 0);
|
||||||
|
}
|
||||||
|
if(method == NN_INETNUM_ISUDP) {
|
||||||
|
req->returnCount = 1;
|
||||||
|
return nn_pushbool(C, (state->inet.protocolsSupported & NN_INET_UDP) != 0);
|
||||||
|
}
|
||||||
|
if(method == NN_INETNUM_ISWEBSOCK) {
|
||||||
|
req->returnCount = 1;
|
||||||
|
return nn_pushbool(C, (state->inet.protocolsSupported & NN_INET_WEBSOCKET) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(method == NN_INETNUM_REQUEST) {
|
||||||
|
if((state->inet.protocolsSupported & NN_INET_HTTP) == 0) {
|
||||||
|
nn_setError(C, "protocol disabled");
|
||||||
|
return NN_EBADCALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nn_checkstring(C, 0, "bad argument #1 (string expected")) return NN_EBADCALL;
|
||||||
|
e = nn_defaultstring(C, 1, "");
|
||||||
|
if(e) return e;
|
||||||
|
if(nn_checkstring(C, 1, "bad argument #2 (string expected")) return NN_EBADCALL;
|
||||||
|
e = nn_defaulttable(C, 2);
|
||||||
|
if(e) return e;
|
||||||
|
if(nn_checktable(C, 2, "bad argument #3 (table expected)")) return NN_EBADCALL;
|
||||||
|
|
||||||
|
ireq.action = NN_INTERNET_CONNECT;
|
||||||
|
ireq.connect.url = nn_tostring(C, 0);
|
||||||
|
ireq.connect.http.postdata = nn_tolstring(C, 1, &ireq.connect.http.postdatalen);
|
||||||
|
size_t headerlen = 0;
|
||||||
|
e = nn_dumptable(C, 2, &headerlen);
|
||||||
|
if(e) return e;
|
||||||
|
ireq.connect.http.headerlen = headerlen;
|
||||||
|
nn_HTTPHeader *headers = nn_alloc(ctx, sizeof(nn_HTTPHeader) * headerlen);
|
||||||
|
if(headers == NULL) return NN_ENOMEM;
|
||||||
|
|
||||||
|
for(size_t i = 3; i < 3 + headerlen * 2; i += 2) {
|
||||||
|
if(!nn_isstring(C, i) || !nn_isstring(C, i+1)) {
|
||||||
|
nn_free(ctx, headers, sizeof(nn_HTTPHeader) * headerlen);
|
||||||
|
nn_setError(C, "malformed headers");
|
||||||
|
return NN_EBADCALL;
|
||||||
|
}
|
||||||
|
headers[i].name = nn_tostring(C, i);
|
||||||
|
headers[i].value = nn_tostring(C, i+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
req->returnCount = 1;
|
||||||
|
e = nn_inetMakeConn(ireq, state->handler, NN_INET_HTTP);
|
||||||
|
nn_free(ctx, headers, sizeof(nn_HTTPHeader) * headerlen);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(C) nn_setError(C, "internet: not implemented yet");
|
||||||
|
return NN_EBADCALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
nn_Component *nn_createInternet(nn_Universe *universe, const char *address, const nn_InternetCard *inet, void *state, nn_InternetHandler *handler) {
|
||||||
|
nn_Component *c = nn_createComponent(
|
||||||
|
universe, address, "internet");
|
||||||
|
if(c == NULL) return NULL;
|
||||||
|
|
||||||
|
const nn_Method methods[NN_INETNUM_COUNT] = {
|
||||||
|
[NN_INETNUM_MAXREADSIZE] = {"maxReadSize", "function(): integer - Returns the maximum size of a read", NN_DIRECT},
|
||||||
|
[NN_INETNUM_ISHTTP] = {"isHttpEnabled", "function(): boolean - Returns whether HTTP/HTTPS support is enabled", NN_DIRECT},
|
||||||
|
[NN_INETNUM_ISTCP] = {"isTcpEnabled", "function(): boolean - Returns whether TCP support is enabled", NN_DIRECT},
|
||||||
|
[NN_INETNUM_ISTLS] = {"isTlsEnabled", "function(): boolean - Returns whether TLS support is enabled", NN_DIRECT},
|
||||||
|
[NN_INETNUM_ISWEBSOCK] = {"isWebsocketEnabled", "function(): boolean - Returns whether WebSocket / WSS support is enabled", NN_DIRECT},
|
||||||
|
[NN_INETNUM_ISUDP] = {"isUdpEnabled", "function(): boolean - Returns whether UDP support is enabled", NN_DIRECT},
|
||||||
|
[NN_INETNUM_CONNECT] = {"connect", "function(address: string, port: number = -1, protocol: string = 'tcp'): userdata - Opens a socket connection and returns a handle to it. Valid protocols are tcp and tls", NN_INDIRECT},
|
||||||
|
[NN_INETNUM_CHANNEL] = {"channel", "function(address: string, port: number = -1, protocol: string = 'websocket', subprotocols?: string[]): userdata - Opens a packet channel and reutrns a handle to it. Valid protocols are websocket and udp", NN_INDIRECT},
|
||||||
|
[NN_INETNUM_REQUEST] = {"request", "function(url: string, portData?: string, headers?: table): userdata - Sends an HTTP/HTTPS requests, returns a handle to it", NN_INDIRECT},
|
||||||
|
};
|
||||||
|
|
||||||
|
nn_Exit e = nn_setComponentMethodsArray(
|
||||||
|
c, methods, NN_INETNUM_COUNT);
|
||||||
|
if(e) { nn_dropComponent(c); return NULL; }
|
||||||
|
|
||||||
|
nn_Context *ctx = &universe->ctx;
|
||||||
|
nn_InternetState *cls = nn_alloc(ctx, sizeof(*cls));
|
||||||
|
if(cls == NULL) {
|
||||||
|
nn_dropComponent(c);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
cls->ctx = ctx;
|
||||||
|
cls->inet = *inet;
|
||||||
|
cls->handler = handler;
|
||||||
|
nn_setComponentState(c, state);
|
||||||
|
nn_setComponentClassState(c, cls);
|
||||||
|
nn_setComponentHandler(c, nn_inetHandler);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2354,12 +2354,94 @@ typedef enum nn_InternetProtocol {
|
|||||||
typedef struct nn_InternetCard {
|
typedef struct nn_InternetCard {
|
||||||
// bitwise OR multiple of them
|
// bitwise OR multiple of them
|
||||||
unsigned char protocolsSupported;
|
unsigned char protocolsSupported;
|
||||||
|
unsigned int maxReadSize;
|
||||||
// per-byte cost of a write
|
// per-byte cost of a write
|
||||||
double transmissionEnergyCost;
|
double transmissionEnergyCost;
|
||||||
} nn_InternetCard;
|
} nn_InternetCard;
|
||||||
|
|
||||||
extern nn_InternetCard nn_defaultInternetCard;
|
extern nn_InternetCard nn_defaultInternetCard;
|
||||||
|
|
||||||
|
typedef struct nn_InternetConnection {
|
||||||
|
nn_InternetProtocol protocol;
|
||||||
|
int port;
|
||||||
|
void *state;
|
||||||
|
nn_uuid id;
|
||||||
|
} nn_InternetConnection;
|
||||||
|
|
||||||
|
typedef struct nn_HTTPHeader {
|
||||||
|
const char *name;
|
||||||
|
const char *value;
|
||||||
|
} nn_HTTPHeader;
|
||||||
|
|
||||||
|
typedef enum nn_InternetAction {
|
||||||
|
NN_INTERNET_DROP,
|
||||||
|
|
||||||
|
// start a connection, remember to check connection->protocol to know which type of connection to establish
|
||||||
|
NN_INTERNET_CONNECT,
|
||||||
|
|
||||||
|
// Ensure a connection is established, error if not.
|
||||||
|
// It is assumed this operation will return whether it is currently connected,
|
||||||
|
// and should be called repeatedly until it works
|
||||||
|
NN_INTERNET_FINISHCONNECT,
|
||||||
|
|
||||||
|
// close a connection
|
||||||
|
NN_INTERNET_CLOSE,
|
||||||
|
|
||||||
|
// read from a connection. Set read.buf to NULL to mark EoF. read.len is the capacity, but also set how much was read in it.
|
||||||
|
NN_INTERNET_READ,
|
||||||
|
|
||||||
|
// write to a connection. HTTP connections do not have this method
|
||||||
|
NN_INTERNET_WRITE,
|
||||||
|
|
||||||
|
// get an HTTP response. Should push the response code, message (ie. OK for 200) and table
|
||||||
|
NN_INTERNET_RESPONSE,
|
||||||
|
} nn_INternetAction;
|
||||||
|
|
||||||
|
typedef struct nn_InternetRequest {
|
||||||
|
nn_Context *ctx;
|
||||||
|
nn_Computer *computer;
|
||||||
|
void *state;
|
||||||
|
const nn_InternetCard *inet;
|
||||||
|
const char *localAddress;
|
||||||
|
nn_InternetConnection *connection;
|
||||||
|
nn_INternetAction action;
|
||||||
|
union {
|
||||||
|
// does a socket connection, for any of the supported protocols
|
||||||
|
struct {
|
||||||
|
// URL of connection
|
||||||
|
const char *url;
|
||||||
|
// port. Useless for HTTP connections, as they should use 80 for HTTP and 443 for HTTPS
|
||||||
|
unsigned short port;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
// HTTP specific
|
||||||
|
size_t postdatalen;
|
||||||
|
const char *postdata;
|
||||||
|
const nn_HTTPHeader *headers;
|
||||||
|
size_t headerlen;
|
||||||
|
} http;
|
||||||
|
struct {
|
||||||
|
const char * const * subprotocols;
|
||||||
|
size_t subprotocolcount;
|
||||||
|
} websockets;
|
||||||
|
};
|
||||||
|
} connect;
|
||||||
|
struct {
|
||||||
|
const char *buf;
|
||||||
|
size_t len;
|
||||||
|
} write;
|
||||||
|
struct {
|
||||||
|
char *buf;
|
||||||
|
size_t len;
|
||||||
|
} read;
|
||||||
|
bool isConnected;
|
||||||
|
};
|
||||||
|
} nn_InternetRequest;
|
||||||
|
|
||||||
|
typedef nn_Exit (nn_InternetHandler)(nn_InternetRequest *req);
|
||||||
|
|
||||||
|
nn_Component *nn_createInternet(nn_Universe *universe, const char *address, const nn_InternetCard *inet, void *state, nn_InternetHandler *handler);
|
||||||
|
|
||||||
// Colors and palettes.
|
// Colors and palettes.
|
||||||
// Do note that the
|
// Do note that the
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user