Skip to content

Commit

Permalink
docs: create first draft of documentation for splashkit online
Browse files Browse the repository at this point in the history
 - information on ExecutionEnvironment, IDBStoredProject and TreeView
 - information on code execution

Yet to run any linting or do thorough proof-reading
Links also not linked yet
  • Loading branch information
WhyPenguins committed Dec 15, 2023
1 parent 3be727b commit 788aa6f
Show file tree
Hide file tree
Showing 5 changed files with 762 additions and 0 deletions.
28 changes: 28 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,34 @@ export default defineConfig({
directory: "products/splashkit/documentation/expansions",
},
},
{
label: "SplashKit Online",
autogenerate: {
directory: "products/splashkit/documentation/splashkit-online",
},
items: [
{
label: "Code Documentation",
autogenerate: {
directory: "products/splashkit/documentation/splashkit-online/code-documentation",
},
items: [
{
label: "Classes",
autogenerate: {
directory: "products/splashkit/documentation/splashkit-online/code-documentation/classes",
},
},
{
label: "Processes",
autogenerate: {
directory: "products/splashkit/documentation/splashkit-online/code-documentation/processes",
},
},
]
},
]
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
title: ExecutionEnvironment - Code Documentation
description: An explanation of what ExecutionEnvironment is, its methods, members, and events.
---

[*executionEnvironment.js*](TODO)

ExecutionEnvironment is a class designed to abstract out running the user's code, and also handle the environment itself (such as resetting variables, preloading files, etc). It contains functions to 'compile' user code, run the main program, reset itself, and create directories/files inside the environment.

The actual implementation can be found inside `executionEnvironment.js`. Upon creation, it creates an iFrame (which can be thought of as a page inside the page) - and this is where all the user's code will be run.
## Why create an iFrame?
The iFrame it creates is sandboxed so that it cannot access anything inside the main page. This is important, since while we can likely trust code the user writes themselves, we cannot trust code they may receive from other people. If we ran the code the user writes directly inside the main page, it could access and manipulate the IDE itself, along with accessing cookies and other things it shouldn't have access to. By running it inside the iFrame, we can be sure it can't access anything it shouldn't.

It also makes it clear which files are part of the project (as those exist outside the iFrame), and which parts are only transient, such as logs (that only exist inside the iFrame and are destroyed on reloads). User code can not permanently overwrite resources.

Additionally, it gives us a way to completely reset the environment the code is running in, as we can destroy and recreate the iFrame without having the reload the main page itself.

To communicate with the iFrame, we can only send and receive messages, which also limits the number of potential escape routes from the iFrame.

To get an idea of what the ExecutionEnvironment does, here are it's methods, members and events:

## Members
- `hasRunOnce` - has the program been run yet? Is reset with `resetEnvironment()`
- `executionStatus` - current status of the program, can be:
- `ExecutionStatus.Unstarted`
- `ExecutionStatus.Running`
- `ExecutionStatus.Paused`

## Methods
- `constructor(container)` - takes a container element to load the iFrame inside.
### Initializing user's code
- `runCodeBlock(block, source)` - takes a code block (which has the block name `block`, and the source code `source`, syntax checks it, and if it passes, sends the code to the iFrame via a message.
- `runCodeBlocks(blocks)` - takes an array of dictionaries with the keys {name, code}, and calls `runCodeBlock` for each one.
### Running user's code
- `runProgram()` - sends a message to the iFrame to run the user's `main` (if it exists).
- `pauseProgram()` - sends a message to pause the user's program - returns a `promise`, that resolves once the program pauses, or fails after 2 seconds.
- `continueProgram()` - sends a message to continue the user's program (if it has been paused)
- `stopProgram()` - sends a message to stop the user's program completely - returns a `promise`, that resolves once the program stops, or fails after 2 seconds.
### Handling the environment
- `resetEnvironment()` - completely resets the environment, by destroying and recreating the iFrame. All files inside the environment will also be lost.
- `cleanEnvironment()` - Does a 'best-efforts' attempt to tidy the environment, such as removing user created global variables. Much faster than `resetEnvironment()`, and does not reset the file system.
### Filesystem
- `mkdir(path)` - sends a message to create a directory
- `writeFile(path, data)` - sends a message to create a directory

## Events
The events can be listened to by attaching with `addEventListener(event, callback)`
- `initialized` - ExecutionEnvironment is setup and ready to execute code.
- `error` - an error has occurred in user code.
Members:
- `message` - the error message
- `line` - the line number of the error
- `block` - the name of the code block the error occurred in.
- `programStarted` - the program has started running
- `programStopped` - the program has stopped running
- `programPaused` - the program has paused
- `programContinued` - the program has resumed running
- `onMovePath` - A file or directory has been moved.
Members:
- `oldPath` - the original path
- `newPath` - the path it was moved to
- `onMakeDirectory` - A directory has been made.
Members:
- `path` - the path to the new directory
- `onDeletePath` - A file or directory has been deleted.
Members:
- `path` - the path to the file/directory
- `onOpenFile` - A file has been opened, possibly for reading or writing.
Members:
- `path` - the path to the file
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: IDBStoredProject - Code Documentation
description: An explanation of what IDBStoredProject is, its methods, members, and events.
---

[*IDBStoredProject.js*](TODO)

IDBStoredProject is a class that handles saving/loading the user's project within the browser itself. It uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB) storage, which allows it to store large amounts of data in a simplified database structure.

It stores a single project inside a single database, creating a new one for each project. It has functions to read and write files to a virtual filesystem saved inside the database (for storing user code, and uploaded resources like sprites and sounds). It also has an area for config, and keeps track of its lastWriteTime inside there.

## Database layout
There are two tables:
- `project` - contains information about the project, such as the last write time. Simple key-value, with the key's name being 'category'.
- `files` - stores all the user's files and directories. Each entry contains the following:
- `nodeID` - a numerical identifier for the node (file/directory), automatically increments.
- `name` - name of the file/directory
- `type` - either `"FILE"` or `"DIR"` - file or directory
- `data` - the file's contents - a binary blob of data. Or `null` if it's a directory.
- `parent` - the `nodeID` of the parent of the file/directory (what directory is it inside). -1 means it is inside the root directory.

## Members
- `initializer` - a function that can be called to initialize the database - performs the equivalent of `skm new`
- `projectName` - the name of the project (and therefore database) it is currently attached to. `null` if detached.
- `lastKnownWriteTime` - the last time the project was written to within this tab.

## Methods
- `constructor(initializer)` - takes an initializer function, used when initializing a project's database for the first time.
- `attachToProject(storeName)` - attached it to a project with the name `storeName`. Initializes the database, and emits an `attached` event.
- `detachFromProject()` - detaches itself from the project, resets its internal state and emits a `detached` event.
- `deleteProject(storeName)` - deletes the project named `storeName`, and returns a promise which resolves once the database is truly deleted.
- `checkForWriteConflicts()` - checks the `lastKnownWriteTime` against the actual `lastWriteTime` inside the database - if they conflict in a way that suggests another tab has written to the database, throw a `timeConflict` event.
- `access(func)` - a bit of a special function. This function is the only entry point to reading/writing to the IDBStoredProject. It takes a function, which it will call, passing in a new object (internally a `__IDBStoredProjectRW`), which has many more methods for reading/writing. This is done, so that the opening/closing of the database can be wrapped around the user function, without them having to handle it manually (and potentially leave open connections causing issues later on). Here's an example of usage:
```javascript
let storedProject = new StoredProject(...)
...
// we get passed a new object, which we called "project", and can use it to get the lastWriteTime.
// this is all performed asynchronously, so we need to "await" it to get the result
let storedTime = await storedProject.access((project)=>project.getLastWriteTime());
// in non-lambda syntax
let storedTime = await storedProject.access(function(project){ return project.getLastWriteTime()});
```
**The following functions are ones accessible from inside the callback to `access` only**

- `getLastWriteTime()` - get the last write time.
- `updateLastWriteTime(time = null)` - set the last write time - defaults to the current time (stored in unix time)
- `mkdir(path)` - make a directory at path, does nothing if it already exists. Emits `onMakeDirectory` event.
- `writeFile(path, data)` - overwrites the data inside the file at `path` with `data` - creates the file if it doesn't exist. Emits `onOpenFile` event. Emits `onWriteToFile` event.
- `rename(oldPath, newPath)` - moves a file/directory to a new path and/or name. Emits `onMovePath` event.
- `readFile(path)` - reads a file at `path` and returns the data inside. Returns `null` if the file doesn't exist.
- `getFileTree()` - returns a complete tree of the file system, in a structure digestible by the `TreeView`.



## Events
The events can be listened to by attaching with `addEventListener(event, callback)`
- `attached` - Is attached and can be used.
- `detached` - Has been detached.
- `onMovePath` - A file or directory has been moved.
Members:
- `oldPath` - the original path
- `newPath` - the path it was moved to
- `onMakeDirectory` - A directory has been made.
Members:
- `path` - the path to the new directory
- `onDeletePath` - A file or directory has been deleted.
Members:
- `path` - the path to the file/directory
- `onOpenFile` - A file has been opened, possibly for reading or writing.
Members:
- `path` - the path to the file
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: TreeView - Code Documentation
description: An explanation of what TreeView is, its methods, members, and events.
---

[*treeview.js*](TODO)

TreeView is a class used for displaying and updating a tree view, designed specifically around file/directory manipulation. It allows viewing multiple filesystems at once in an overlapping fashion (important since we have the files in the user's project that will be saved/loaded, and also the live files inside the ExecutionEnvironment, which may be different). It allows files/folders to be dragged around and organized, and folders to have a button on the side for uploading new files.

The way it is intended to be used, is to make it listen to events from the target filesystems (such as file moves/deletes), and update itself accordingly. When it is interacted with by the user, it will emit its own events - these events should be listened to, and the target filesystem updated accordingly. It should then look like this :
1. A file is created in the target filesystem and an event is emitted
2. The TreeView reacts to this event and creates a node in its tree with the same name.
3. The user now drags that node to inside another node (directory), and the TreeView emits an event.
4. A function is called back from this event, that then tells the target filesystem to move the file.
5. The target filesystem moves the file, and an event is emitted.
6. The TreeView reacts to this event, and moves the node to inside the directory.

See how the TreeView never updates itself - it relies on an event coming _back_ from the target filesystem. This means that if the target filesystem fails to do the operation for whatever reason, the TreeView also remains in the same state, meaning the two remain synchronized effectively.

See example usage of it inside `fileview.js`, where it is attached to both the `IndexedStorageDB` filesystem, and also the filesystem inside the `ExecutionEnvironment`.

### Limitations
Currently there is no way to delete files/folders, or rename files/folders in the interface itself. This shouldn't be hard to add, however.

## Members
None publicly available.

## Methods
- `constructor(container, FSes)` - takes a container to place the TreeView's elements into, and a list of FSes, which are the filesystems it will support. An example list looks like this `{"persistent":"node-persistent", "transient":"node-transient"}`, key-value pairs where the key is the filesystems name, and the value is a css style to apply to nodes inside this filesystem.
- `moveNode(oldPath, newPath, index = -1, FS)` - moves a node to a new path and/or name. Allows one to set the index the node will appear at, and also which filesystem(s) (a list) the move occurred in.
- `deleteNode(path, FS)` - deletes a node from a set of filesystem(s) (a list)
- `addDirectory(path, FS)` - make a directory at path, does nothing if it already exists. Allows one to set which filesystem(s) (a list) the directory add occurred in.
- `addFile(path, data)` - make a file at path, does nothing if it already exists. Allows one to set which filesystem(s) (a list) the file was added in.
- `reset(path)` - Deletes all nodes.
- `populatefileView(files, FS)` - Populates the tree with a list of files in a particular structure (the same one `IDBStoredProject.getFileTree()` returns). Allows one to set which filesystem(s) (a list) the directory add occurred in.

## Events
The events can be listened to by attaching with `addEventListener(event, callback)`
- `nodeMoveRequest` - A file or directory has been moved.
Members:
- `treeView` - the TreeView object
- `oldPath` - the original path
- `newPath` - the path it was moved to
- `FS` - the filesystem(s) the change occurred in.
- `accept` - a function that can be called to announce that the change was successful - **currently unused**.
- `folderUploadRequest` - The 'add file' button was clicked on on a directory.
Members:
- `treeView` - the TreeView object
- `path` - path to the directory
- `FS` - the filesystem(s) the directory exists in.
- `nodeDoubleClick` - A file node has been double clicked.
Members:
- `treeView` - the TreeView object
- `path` - path to the file
- `FS` - the filesystem(s) the directory exists in.
Loading

0 comments on commit 788aa6f

Please sign in to comment.