docs/,editor/,runner/- nothing in here besides the HTML shells for the respective pages. Some of the documentation here is hand-written in Markdown which is then rendered after the page loads bymdAll.ts.build/- output JS files, sourcemaps, and metadata. This is all automatically generated, don't touch it. It has to be committed for the github pages to work without me having to make a custom pages build action. I never figured that part out.src/- TS source files.src/common/- things that are shared between editor, runner, and documentation pages, that implement the real code functionality of Lynx.src/editor/- the main files and linter wrapper for the editor page (it isn't all one file, so I put them in a folder)src/nodes/- definitions for all of the node types that Lynx implements. This folder is a little bit more disorganized than the rest so if you send me a new node definition don't be surprised if I end up putting it in a seemingly random file.
git clonethis repository or your fork of it locally.- Make sure you have dependencies installed. I use pnpm and the
pnpm-lock.yamlis committed so installation should just be as simple as opening a terminal and runningpnpm i. If you usenpmoryarnit should still work but be slower. I have.gitignore'd bothnpm-lock.jsonandyarn.lockso that only the pnpm lockfile will actually be committed. - Open a background terminal and run
make. This will start a nodemon daemon which will rebuild everything when you save a source file, plus a Python web server which will make your local Lynx build available at http://localhost:8000/. - If you don't need or want to open it in the browser, or nodemon isn't working for you, just run
make buildto build everything once and exit.make servewill start the Python web server separately.
Nodes should not try to implement everything all at once. Each node should just be one "operation" (however you define it) and should store as little state as possible.
- Find a good place to put the node definition.
- If it is in an existing file, just call
defNode()with the definition, and you're good. - If you want to create a new file, make sure to import
defNode()andPort, and then add your new file to the list of imports in all.ts so that it will be picked up.
- If it is in an existing file, just call
- Try to copy the same kind of definition as existing nodes, and don't duplicate functionality that could be done with an existing node. (It is OK right now to add a new node if it could still be implemented by several existing nodes linked to each other; because I haven't implemented subgraph templates, doing this on the Scheme side would be rather tedious.)
- The
stateKeyslist does literally nothing at runtime and is only there so that you can "declare" the state variables you will be using, and then TypeScript autocomplete can help you when you accessnode.state. - TypeScript will help you a lot catching node port name errors. If you feel you need to use
// @ts-ignore-- don't!
(Features are global objects that implement some shared function, and aren't created initialized multiple times on multiple nodes. If Lynx is an entity-component-system kind of architecture, the nodes are the entities, the node definitions are the components, and the features are the systems.)
- Same setup as for adding a node. Typically the feature goes in the same file as the nodes that use it.
defFeature()works differently thandefNode(); since two different node definitions can (technically) have the same ID,defNode()just chucks all the definitions into a flat unkeyed list. However, features must be uniquely named, sodefFeature()expects the feature ID as a separate parameter.- Also unlike node definitions, feature definitions are a class, not an interface. You need to call the
Featureconstructor, and it takes an async initialization function that will be run if any nodes that use the feature are created, and another object which are the feature's methods and properties. The properties will beObject.assign()'ed to the feature object. - To create a node that uses this feature:
- Create the rest of the definition as described above.
- Add the feature's ID to the
featureslist property. - The
setup()hook in the node definition will now get afeaturesobject, that maps feature ID to the actual object. You may have to use square bracket notation to access things depending on the ID of the feature. - When you try to use the feature object's methods, TypeScript will yell at you about no-such-property or something because it doesn't know the type of the feature object. Until I actually figure out how to type this, use
// @ts-expect-errorwhen you invoke the feature's methods or properties. This is the only place where you are allowed to use// @ts-expect-error.
- Create a new
.scmfile inexamples/files. - At the bare minimum, the metadata fields
*title*,*category*, and*description*are required. - When defining named nodes, try to stick to
kebab-casefor the names. - If you are creating a more full-featured app instead of just a small demo, these metadata fields are strongly recommended:
*author*(your name or username),*author-url*(a link to your website), and*version*(a number; you don't need all that semver mess here). - If your example isn't even meant to be a demo of anything, and only serves as a regression test, set the metadata field
*hidden*to true (#t) so it will not show up anywhere.- If you do this, technically only the
*hidden*is required and title, category, and description are optional, but I still recommend using them. - If it is truly a test, set the category to
"Testing". - You can still access the example using the
?example=test-idURL parameter in both the editor and runner. It just won't show up in the examples list page or editor dropdown.
- If you do this, technically only the