diff --git a/docs/index.md b/docs/index.md index 1b73cee..040d3bb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,4 +6,100 @@ # Catwork -This is a landing page. \ No newline at end of file +## The cat framework, written by cats, for cats! + +Catwork is a tiny, ergonomic, declarative framework for creating runtime code. + +--- + +## Weave dependencies with asynchronous design patterns. + +Catwork implements all of its code through Fragments! These allow you to manage +dependencies in a simple and easy to understand way! + +```lua title="MeowService.lua" +return Catwork.Fragment { + Init = function(self) + task.wait(1) + self.Value = "cat" + end, + + ConsumeValue = function(self) + print(self.Value) + end +} +``` + +```lua +local MeowService = require(script.MeowService) + +MeowService:Await() -- waits for the service to initialise before grabbing the value + +-- If we called this without awaiting, it'd print nothing, Catwork wraps messy +-- asynchronous design behind `Await` and `HandleAsync` +MeowService:ConsumeValue() +``` + +--- + +## You build the framework! + +Catwork exports an object called a `Service`, this lets you add almost any +behaviour to Fragments beyond what Catwork originally allows. + +```lua title="RemoteService.lua" +return Catwork.Service { + Name = "RemoteService", + + Fragment = function(self, params) + params.Remote = makeRemote(params) + return Catwork:CreateFragmentForService(params, self) + end, + + Spawning = function(self, f) + if f.RemoteConnection then + f.Remote.OnServer:Connect(f.RemoteConnection) + end + end +} +``` + +```lua + +local MeowRemote = RemoteService:Fragment { + Name = "MeowRemote", + + RemoteConnection = function(plr) + print(`meows as {plr.Name} cutely`) + end +} +``` + +--- + +## Run Fragments, anywhere. + +Catwork doesn't care about what you return, only that you call one of it's +constructors. + +This means as long as the code is executed, you can create a Fragment just about +anywhere, a ModuleScript? A Script? Go for it! + +```lua title="LocalScript in ReplicatedFirst" +local Catwork = require(ReplicaedFirst.Catwork) + +local LoadingScreenManager = Catwork.Fragment { + Name = "LoadingScreenManager", + + Init = function() + ReplicatedFirst:RemoveDefaultLoadingScreen() + -- code to execute guis or whatever + end +} +``` + +--- + +# Lets go on an adventure together + +Ready to start with Catwork? Then lets go into the tutorials! \ No newline at end of file diff --git a/docs/reference/fragment.md b/docs/reference/catwork/fragment.md similarity index 98% rename from docs/reference/fragment.md rename to docs/reference/catwork/fragment.md index 539f8de..5badbb0 100644 --- a/docs/reference/fragment.md +++ b/docs/reference/catwork/fragment.md @@ -1,5 +1,7 @@ # Fragment +The basic building block of all running Catwork code. + !!! note Due to the dynamic nature of Fragments, this documentation only describes the built-in logic. diff --git a/docs/reference/catwork.md b/docs/reference/catwork/index.md similarity index 100% rename from docs/reference/catwork.md rename to docs/reference/catwork/index.md diff --git a/docs/reference/catwork/service.md b/docs/reference/catwork/service.md new file mode 100644 index 0000000..9617773 --- /dev/null +++ b/docs/reference/catwork/service.md @@ -0,0 +1,182 @@ +# Service + +Services are singletons that let you change how Fragments behave. + +??? tip "TemplateServices are now implicit" + Catwork previously had an explicit method called `Catwork.TemplateService`, + this has since been removed. You now simply need to use one of two methods + to enable templates + + === "Explicit" + ```lua + return Catwork.Service { + EnableTemplates = true + } + ``` + === "Implicit" + ```lua + return Catwork.Service { + TemplateAdded = function(self, template) + + end + } + ``` + +## Properties + +### Service.EnableTemplates + +`boolean` + +Dictates if the service is a TemplateService, and that methods such as `Template` +can be used. + +!!! warning "Assume template methods are undefined if this property is `false`" + This setting disables all template logic, however, some objects may be left + over such as the `Service.Templates` table. + +### Service.Fragments + +`{[string]: Fragment}` + +A table representing which fragments belong to this specific Service, these +are stored the same way as Catwork.Fragments(1). +{ .annotate} + +1. Fragments are stored using the format + `{Fragment.Service.Name}_{Fragment.ID}` + +### Service.FragmentNameStore + +`{[string]: {[string]: Fragment}}` + +An internal storage tree for `Service.GetFragmentsOfName`. + +!!! danger "Do not use this property externally" + This table is unpredictable, it is safer to use `GetFragmentsOfName` as this + returns an immutable chunk of this data + +### Service.Templates + +`{[string]: Template}` + +A storage table of this service's templates. + +## Callbacks + +These are built-in lifecycle callbacks that you can define in your Service. +They are generally defined as the following syntax in `Catwork.Service`: + +```lua +Catwork.Service { + Fragment = function(self, params) + + end +} +``` + +### Service.Fragment + +Defined as `Fragment = function(self, params)` + +The main method for constructing fragments out of the service. This method +is provided with a list of parameters that are then built into a Fragment. + +```lua +Fragment = function(self, params) + function params:Meow() + return "meow!!!" + end + + return Catwork:CreateFragmentForService(params, self) +end +``` + +This is eventually transformed into the method `Service:Fragment`. + +!!! warning "You MUST call `CreateFragmentForService`" + This method links up everything internal related to the fragment, and must + be called **once** in your `Fragment` callback definition. + +### Service.FragmentAdded + +Defined as `FragmentAdded = function(self, Fragment)` + +This method is invoked just after a `CreateFragmentForService` call, and +represents the actual Fragment, and not just a tree of constructed parameters. + +This callback is intended for setting up code around the fragment, instead of +enforcing it's shape. + +### Service.FragmentRemoved + +Defined as `FragmentRemoved = function(self, Fragment)` + +Invoked after a Fragment has been destroyed, this method should be used for +definining internal cleanup logic. + +### Service.Spawning + +Defined as `Spawning = function(self, Fragment)` + +The asynchronous entry point that is called when `Fragment:Spawn` is called. +Since this callback executed asynchronously, it is safe to perform stateful code +here as it's escaped from the declarative callbacks of `Fragment` and `FragmentAdded`. + +??? question "Fragment, FragmentAdded or Spawning?" + `Service.Fragment` should be used for setting up the Fragment itself, but + should not perform any runtime behaviour. + + `Service.FragmentAdded` should react to constructed fragments, and may perform + simple runtime actions that do not require state. FragmentAdded should also (but + does not have to) call `Spawn`. + + `Service.Spawning` runs in an asynchronous thread, and stateful runtime behaviour + should be performed here. + +### Service.TemplateAdded + +Defined as `TemplateAdded = function(self, Template)` + +Invoked when a new Template is added through `Service:Template`. The definition +of this key implicitly enables `TemplateServices`. + + +## Methods + +## Service:CreateFragmentFromTemplate + +`(Template|string, A) -> Fragment` + +Creates a fragment from a given template, or template identifier. This function +will error if the template identifier is nil. + +!!! danger "Dont give this templates from other services" + This creates undefined behaviour, keep templates to their own service. + +## Service:GetFragmentsOfName + +`(name: string) -> {[string]: Fragment}` + +Returns an immutable chunk from `Service.FragmentNameStore`, of all fragments +defined with the given name. Keys of the return table are full fragment IDs(1) +of each fragment. +{.annotate} + +1. Fragments are stored using the format + `{Fragment.Service.Name}_{Fragment.ID}` + +## Service:Template + +`(TemplateParams) -> Template` + +Builds a template from the given params table. + +```lua +Service:Template { + Name = "CatGenerator", + CreateFragment = function(self, fragment) + + end +} +``` \ No newline at end of file diff --git a/docs/reference/catwork/template.md b/docs/reference/catwork/template.md new file mode 100644 index 0000000..052a8dc --- /dev/null +++ b/docs/reference/catwork/template.md @@ -0,0 +1,29 @@ +# Template + +Templates are small factory objects that create Fragments. Unlike other objects, +they are incredibly simple. + +## Properties + +### Template.Name + +`string` + +The unique identifier for the template, each Service may only have one template +of the given name. + +### Template.Service + +`Service` + +The service this template originates from, and should be the only service you +create it with. + +## Methods + +### Template:CreateFragment + +`(A) -> Fragment` + +Builds a Fragment using the template and given parameters, returns the Fragment +after being created. \ No newline at end of file diff --git a/docs/reference/errors.md b/docs/reference/errors.md new file mode 100644 index 0000000..3b7e301 --- /dev/null +++ b/docs/reference/errors.md @@ -0,0 +1,227 @@ +# Errors + +This is a reference document for all errors. + +!!! todo + This should be referenced to in the ERROR module + +## BAD_ARG + +* Message : `Bad argument number %s to function %q. Expected %s, got %s` +* Severity: `Error` + +An argument was passed to a function that doesn't match an expected type. + +```lua +Catwork.Fragment("cat") -- Not OK +Catwork.Fragment {} -- OK +``` + +## BAD_OBJECT + +* Message: `Bad argument number %s to function %s. Type %s could not be converted into object %s.` +* Severity: `Error` + +Catwork uses internal headers to identify what tables you're passing to +functions. This error emits when an incorrect object was passed. + +```lua +Catwork:CreateFragmentForService({}, {}) -- Not OK, expects Service on second arg +Catwork:CreateFragmentForService({}, SomeService) -- OK +``` + +## BAD_SELF_CALL + +* Message: `Bad self call to %q, did you mean to use : instead of .?` +* Severity: `Error` + +For some methods, we can evaluate specifically if you've sent the correct +object as `self` in the method. When you do `:` calling, this isn't an issue, +however, if you call with `.`, for example, inside a pcall, you must also +pass the table as `self` + +[More information on method call sugar syntax](https://appgurueu.github.io/2023/07/26/lua-syntactic-sugar.html#calls) + +!!! note + You should generally use `:` syntax where possible on methods where it is + defined, because its actually slightly faster. You should only fallback to + `.`, if you're trying to index the function, for example, inside a pcall + + ```lua + pcall(Catwork.GetFragmentsOfName, Catwork, "cat") + ``` + +```lua +Catwork.GetFragmentsOfName("cat") -- Not OK, self was not passed +Catwork.GetFragmentsOfName({}, "cat") -- Not OK, wrong object was not passed +Catwork.GetFragmentsOfName(Catwork, "cat") -- OK, catwork passed as self +Catwork:GetFragmentsOfName("cat") -- OK, method call syntax +``` + +## BAD_TABLE_SHAPE + +* Message: `Object %* cannot be converted to %s. Type of key %s is invalid. Expected %q, got %q.` +* Severity: `Error` + +The `Catwork.Fragment` and `Catwork.Service` constructors validate their shape, +Fragments are loosely checked since they can be expanded, but Service is strictly +checked. + +If you get this error, it means you've defined a key with the wrong type. + +```lua +Catwork.Fragment { + Name = 0 -- < this causes an error + + --- + Name = "Cat" -- < this is ok +} +``` + +## BAD_TEMPLATE + +* Message: `Template %s does not exist for Service %*.` +* Severity: `Error` + +This error is emitted when you call `Service:CreateFragmentFromTemplate` using a +string key representing a template that doesn't exist. + +!!! tip "Use Templates directly where possible" + Its safer to use templates directly, as referencing them by string can result + in the template not being defined. + + Many services abstract the CreateFragmentFromTemplate function behind some + other constructor, as this mechanism is fairly internal. + +## DEPRECATED + +* Message: `Function %q is deprecated. Use %q instead.` +* Severity: `Warn` + +The method is deprecated, and the alternative should be used. + +## DISPATCHER_ALREADY_SPAWNED + +* Message: `Fragment %* has already been spawned.` +* Severity: `Error` + +This appears when you try to spawn a Fragment multiple times, if you're trying +to capture the result of a Spawn call, use `Await` or `HandleAsync` instead. + +## DISPATCHER_DESTROYED_FRAGMENT + +* Message: `Fragment %* cannot be spawned because it has been destroyed.` +* Severity: `Error` + +This error is invoked when you try to spawn a destroyed Fragment, because +destroyed Fragments dont appear in any of Catwork's internal storage tables, +they cant be spawned. + +## DISPATCHER_SPAWN_ERR + +* Message: `A fragment experienced an error while spawning: %s` +* Severity: `Error` + +Shown as a fail-state of the `xpcall` that `Service.Spawning` is wrapped into, +you should try to track down this error in your own code. + +## DUPLICATE_FRAGMENT + +* Message: `Fragment %s is already defined` +* Severity: `Error` + +When using a static ID, they must be unique per service. You may have multiple +Fragments with the same ID as long as they are not in the same Service. + +## DUPLICATE_SERVICE + +* Message: `Service %s is already defined.` +* Severity: `Error` + +Service names must be unique, this error is shown when you try to create an +already existing Service. + +## FRAGMENT_SELF_AWAIT + +> Future addition, not currently implemented + +* Message: `Fragment %* is awaiting upon itself and will never resolve. Use HandleAsync instead.` +* Severity: `Warn` + +This is warned when you call `self:Await` inside an Init callback directly or +indirectly, you should instead use `self:HandleAsync` + +=== "Bad" + + ```lua + Init = function(self) + self:Await() -- this will never resolve + end + ``` + +=== "Better" + + ```lua + Init = function(self) + self:HandleAsync(function(ok, err) + -- this will resolve as it runs outside of the Init callback + end) + end + + ``` + + +## GUID_IDS_NOT_ALLOWED + +* Message: `Cannot use Fragment ID %s, a new ID has been generated.` +* Severity: `Warn` + +GUIDs cannot be used as static IDs, as this is the fallback state for non-static +fragments. If you want to statically use a GUID identifier, add a prefix char +such as `_`. + +## INTERNAL + +* Message: `Error: %*. This is likely a known internal error, please report it!` +* Severity: `Error` + +This only appears when a known internal error occurs, if this does appear, please +report it and what you're doing, as it likely means we're trying to track down +a bug. + +## SERVICE_DUPLICATE_TEMPLATE + +* Message: `Template %s already exists` +* Severity: `Error` + +Services store templates with string identifiers, and only one Template may +exist per identifier. + +## SERVICE_NO_TEMPLATES + +* Message: `Service %* does not implement templates.` +* Severity: `Error` + +The service is not a TemplateService, and as such, `Service.Template` cannot +be used on it. + +!!! tip + If you're trying to create a TemplateService, you can auto-enable them either + through the existence of a `TemplateAdded` callback, or you can explicitly + enable it with `EnableTemplates` + + ```lua + return Catwork.Service { + EnableTemplates = true -- explicit definition + + TemplateAdded = function() -- implicit definition + } + ``` + +## UNKNOWN + +* Message: `Unknown Error` +* Severity: `Error` + +The internal error emitter was called with an invalid identifier. Please report +this if it happens. \ No newline at end of file diff --git a/docs/reference/service.md b/docs/reference/service.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/reference/template.md b/docs/reference/template.md deleted file mode 100644 index e69de29..0000000 diff --git a/mkdocs.yml b/mkdocs.yml index 5534194..3d5d8bd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -27,11 +27,21 @@ theme: - navigation.sections - navigation.instant - navigation.indexes + - navigation.footer - search.suggest - search.highlight + - content.code.copy + - content.code.select + - content.code.annotate + name: material +extra: + social: + - icon: fontawesome/brands/mastodon + link: https://tech.lgbt/@metatablecat + extra_css: - stylesheet/extra.css @@ -39,10 +49,12 @@ nav: - Home: index.md - Reference: - reference/index.md - - Catwork: reference/catwork.md - - Fragment: reference/fragment.md - - Service: reference/service.md - - Template: reference/service.md + - Catwork: + - reference/catwork/index.md + - Fragment: reference/catwork/fragment.md + - Service: reference/catwork/service.md + - Template: reference/catwork/template.md + - Errors: reference/errors.md markdown_extensions: @@ -50,13 +62,18 @@ markdown_extensions: - attr_list - meta - md_in_html - - pymdownx.superfences + - toc: + permalink: true + + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.betterem - pymdownx.details - pymdownx.tabbed: alternate_style: true - pymdownx.inlinehilite - - toc: - permalink: true - pymdownx.highlight: guess_lang: false \ No newline at end of file