Flamework is an extensible game framework. It requires typescript and offers many useful features and abstractions.
In-depth documentation, and installation steps, are hosted on my website.
Flamework is an extensible game framework. It requires typescript and offers many useful features.
License: MIT License
Flamework is an extensible game framework. It requires typescript and offers many useful features and abstractions.
In-depth documentation, and installation steps, are hosted on my website.
this.attributes.points++
const x = ++this.attribute.points
const y = this.attribute.points *= 2
this.attribute.points = 0;
this.instance.SetAttribute("points", this.attribute.points + 1);
const value_0 = this.attribute.points;
this.instance.SetAttribute("points", value_0 + 1);
const x = value_0;
const value_1 = this.attribute.points * 2;
this.instance.SetAttribute("points", value_1);
const y = value_1;
this.instance.SetAttribute("points", 0);
(Kind of related to #33, but also different)
Currently, there is no possibility to retrieve all components that extend from a base component, which would be a great feature for writing decoupled code.
For example, I have different types of Interaction Classes for things like Seats or Heal Stations, that all derive from a BaseInteraction
Class for common functionality. Then, I have a GUI to display an interaction prompt on all of these. Currently, I have to call getAllComponents()
for each of these Interaction Classes manually (Which also means I have to reference them), instead of using one getAllComponents()
call that supports Polymorphism.
I know that could be hard to implement as these types don't really exist on runtime, but I could imagine something like this working through the metadata that Flamework defines for each Component.
all of the below are currently possible, using internal Flamework apis, but I'd like to expose a proper API for doing all of the above.
requesting feedback on potential use cases and what people would generally want this API to include
this issue addresses @flamework/components, but it doesn't seem to let me make issues over there.
When an object with a tag that is used for a component is parented to workspace on the server, it's pretty much guaranteed to fail any instance guard on the component just because there hasn't been a chance for the descendants to load in yet.
One potential solution is to just slap a RunService.Heartbeat:Wait()
before doing anything when a tag is added to an object. This works perfectly when StreamingEnabled is off, but definitely wouldn't work otherwise.
Could maybe just wait for all required children to get added before doing anything? but this might end up confusing the fuck out of people when a component instance never gets made for something without error because it has the wrong children
Property assignments are handled prior to to a component being setup which can cause issues at runtime (instance
and attributes
aren't valid)
All property assignments and constructor body (?) should be implicitly moved into onStart
Knit recently added memory categories for services/controllers, is this a feature that could be supported by Flamework in the future? It'd be incredibly helpful.
Sleitnick/Knit@024a892
support async remote function callbacks and make function middleware asynchronous
Expose metadata about Flamework (e.g config) to Flamework's runtime
Related #28
Log each step/service in ignition for debugging purposes.
Rest parameters will incorrectly use an array guard instead of specifically denoting a value as a rest parameter
Ignore in unions, compile to t.any
when a value is necessary
https://github.com/rbxts-flamework/test-project
I'm trying to use Components.getComponent()
in component A's onStart
event to resolve a reference to component B, which it has a dependency on. Based on the lifecycle documentation which states:
It's also not recommended to use other dependencies inside, or prior to, OnInit. Only important initialization should be done here, and the rest can be done in the OnStart lifecycle event.
I was expecting that I could do this, but it appears that not all components are added to the instance at the point that onStart
events are triggered.
Love the idea of hash, let's make it better!
allow us to disconnect onAttributeChanged
Flamework wasn't compiling Flamework.addPaths
correctly to Flamework._addPaths, and so I just updated flamework and it's transformer then now I'm back to getting this error:
[Flamework]: Failed to load! TS version mismatch detected
[Flamework]: Flamework is using TypeScript version 4.5.2
Flamework]: roblox-ts requires TypeScript version 4.3.5
[Flamework]: You can fix this by setting your TypeScript version: npm install -D typescript@=4.3.5
And this is pretty annoying because, back when I started this project I had to deal with this then I had to use the template and now I'm back to this again and it took so much of my time and I even did the suggestion to fix it but it didn't work.
Constructors that yield are useful for setting up class members using functions like .WaitForChild()
. The component will not setup any lifecycle events until the constructor is complete. This allows for patterns like:
@Component({ tag: "CharacterComponent" })
export class CharacterComponent extends BaseComponent<{}, Model> implements OnPhysics {
private humanoid: Humanoid;
constructor() {
super();
const humanoid = this.instance.WaitForChild("Humanoid", 5);
assert(humanoid && humanoid.IsA("Humanoid"));
this.humanoid = humanoid;
}
onPhysics() {
// use this.humanoid
}
}
removeComponent
would need to wait for the constructor to finish before calling destroy()
replace type parameters with object type with renamed entries
interface MyObject {
a: string,
b: number,
}
interface ServerEvents {
myEvent<T extends keyof MyObject>(key: T, value: MyObject[T]): void;
}
myEvent("a", 15); // invalid, but passes
myEvent("b", "string"); // invalid, but passes
myEvent("a", "string"); // valid
myEvent("b", 15); // valid
myEvent("c", 15); // invalid
Currently, flamework uses a very hard coded method for generating internal IDs, which attempts to serialize the package name and it's output path relative to the package directory (e.g out/my/file.ts@MyClass)
This is not very flexible, and has the following issues/limitations
flamework.build
must be put in the project directory, which means to recompile you have to delete flamework.build
instead of just the out directory (fixed via separate solution rbxts-flamework/transformer@298df43)flamework.build
and with an out
directory.Recently, I've improved how flamework finds build infos rbxts-flamework/transformer@3a18957 which will allow me to put the flamework.build
under the out directory, which will then allow precompiled flamework dependencies to be put anywhere and be successfully resolved and usable as long as a flamework.build
is included. The internal id's path can then be calculated relative to the nearest flamework.build
This should remove unnecessary limitations, fewer hard coded aspects, and account for more use cases.
厉害呀!很需要这个,希望也能做出点贡献
Support constructor dependency injection for components to other components. This will solve many race conditions relating to components depending on eachother.
#31 is similar but involves deferring and instantiating components, if already scheduled, when calling getComponent
The actual signature is getComponent<T>(instance: Instance, componentSpecifier?: Constructor<T> | string)
but the index.d.ts
file in the NPM package does not allow for string specifiers to be passed in.
Hi!
It would be pretty handy to be able to register my own "instances" into the framework's DI container. I love working with DI, but I couldn't find a way to register a logger I usually use (roblox-aurora/rbx-log
) to be used with flamework's DI system, it doesn't need to be anything fancy, something like this would do just fine:
Flamework.addDependency(MY_DEPENDENCY_INSTANCE)
Client side type checks aren't necessary in production and should be able to be disabled.
enum A {}
Flamework.createGuard<A>();
A predicate for determining whether an instance should be ignored when attaching components to instances.
I have a remote function set up to update PlayerData, which looks like this:
interface ServerFunctions {
updatePlayerData(newData: Partial<PlayerData>): PlayerData | undefined;
}
When attempting to compile this it exceeds the maximum call stack size and does not compile, is there any way to rectify this? Because this function is designed to take in PlayerData that should be updated, and then on the server it checks if the player that invoked the function has access to change the data that they changed, if they do it will return the new data to them, otherwise it will return nothing.
By removing Partial
it compiles, but I specifically want that the function to be used to update parts of the data, without having to reconstruct the complete data and only changing the value that you want changed.
Versions:
Stack trace:
RangeError: Maximum call stack size exceeded
at Object.getExpressionPrecedence (MY_PATH\node_modules\roblox-ts\node_modules\typescript\lib\typescript.js:17268:24)
at parenthesizeExpressionForDisallowedComma MY_PATH\node_modules\roblox-ts\node_modules\typescript\lib\typescript.js:21222:43)
at Object.sameMap (MY_PATH\node_modules\roblox-ts\node_modules\typescript\lib\typescript.js:668:30)
at Object.parenthesizeExpressionsOfCommaDelimitedList (MY_PATH\node_modules\roblox-ts\node_modules\typescript\lib\typescript.js:21217:29)
at Object.createCallExpression (MY_PATH\node_modules\roblox-ts\node_modules\typescript\lib\typescript.js:23293:51)
at Object.call (MY_PATH\node_modules\rbxts-transformer-flamework\out\util\factory.js:101:24)
at buildGuardFromType (MY_PATH\node_modules\rbxts-transformer-flamework\out\util\functions\buildGuardFromType.js:147:28)
at buildGuardsFromType (MY_PATH\node_modules\rbxts-transformer-flamework\out\util\functions\buildGuardFromType.js:47:29)
at buildGuardFromType (MY_PATH\node_modules\rbxts-transformer-flamework\out\util\functions\buildGuardFromType.js:242:90)
at buildGuardsFromType (MY_PATH\node_modules\rbxts-transformer-flamework\out\util\functions\buildGuardFromType.js:47:29)
(node:11852) UnhandledPromiseRejectionWarning: RangeError: Maximum call stack size exceeded
at Object.getExpressionPrecedence (MY_PATH\node_modules\roblox-ts\node_modules\typescript\lib\typescript.js:17268:24)
at parenthesizeExpressionForDisallowedComma (MY_PATH\node_modules\roblox-ts\node_modules\typescript\lib\typescript.js:21222:43)
at Object.sameMap (MY_PATH\node_modules\roblox-ts\node_modules\typescript\lib\typescript.js:668:30)
at Object.parenthesizeExpressionsOfCommaDelimitedList (MY_PATH\node_modules\roblox-ts\node_modules\typescript\lib\typescript.js:21217:29)
at Object.createCallExpression (MY_PATH\node_modules\roblox-ts\node_modules\typescript\lib\typescript.js:23293:51)
at Object.call (MY_PATHA\node_modules\rbxts-transformer-flamework\out\util\factory.js:101:24)
at buildGuardFromType (MY_PATH\node_modules\rbxts-transformer-flamework\out\util\functions\buildGuardFromType.js:147:28)
at buildGuardsFromType (MY_PATH\node_modules\rbxts-transformer-flamework\out\util\functions\buildGuardFromType.js:47:29)
at buildGuardFromType (MY_PATH\node_modules\rbxts-transformer-flamework\out\util\functions\buildGuardFromType.js:242:90)
at buildGuardsFromType (MY_PATH\node_modules\rbxts-transformer-flamework\out\util\functions\buildGuardFromType.js:47:29)
Flamework attaches metadata to classes that need it, which is done via Flamework.registerMetadata.
This can be a bit awkward with inheritance, limits customizability, and also does not support adding metadata to properties/methods.
A reasonable alternative to Flamework.registerMetadata would be an API similar to reflect-metadata.
This would allow a lot of customizability for end users, and allow properties and methods to have custom metadata (e,g parameter types, return type, etc)
rbxtsc init game
+ degit?@flamework/core
@flamework/components
@flamework/networking
the current solution for guarding the type of a component looks a little akward:
import { Component, BaseComponent, OnStart, Flamework } from "@rbxts/flamework";
interface Attributes {}
const guard = Flamework.createGuard<Model>();
@Component({
tag: "Structure",
})
export class Structure extends BaseComponent<Attributes> implements OnStart {
structure!: Model
onStart() {
if (!guard(this.instance)) {
error("no")
}
this.structure = this.instance
}
}
It would be much simpler for users if you added a second generic to BaseComponent and then did your voodoo ts plugin shit to automatically add a type guard to the component
import { Component, BaseComponent, OnStart, Flamework } from "@rbxts/flamework";
interface Attributes {}
@Component({
tag: "Structure",
})
export class Structure extends BaseComponent<Attributes, Model> implements OnStart {
onStart() {
print(this.instance.PrimaryPart) //wooo this is legal because this.instance is a model
}
}
When an onAttributeChanged
callback is registered on the server, the oldValue parameter passed to the callback is always the current attribute value instead.
Quick test case that can be dropped into a fresh project as a shared component. The client-side output is what you would expect, but the server-side output is not.
import { BaseComponent, Component } from "@flamework/components";
import { OnStart } from "@flamework/core";
import { RunService } from "@rbxts/services";
@Component({
tag: "Test",
defaults: {
TestValue: 0,
},
})
export class TestComponent extends BaseComponent<{ TestValue: number }> implements OnStart {
constructor() {
super();
this.onAttributeChanged("TestValue", (newValue, oldValue) => print(`Value changed from ${oldValue} to ${newValue}`));
}
onStart(): void {
if (RunService.IsServer()) {
task.spawn(() => {
while (true) {
this.attributes.TestValue = math.random(0, 99);
task.wait(5);
}
});
}
}
}
improves editing experience, e.g
getAllComponents<T>()
Maybe there's a better way to achieve what I'm trying to do, but it seems like the ability to retrieve a component based on the tag used to instantiate it would be helpful, because the tag is an identifier that isn't tightly coupled to the component's implementation like the specifier is.
I have weapon components in my shared code, which are intended to be usable by both players and NPCs. I have a client-only player input component and a server-only NPC input component, with the same public API. From the weapon code, I want to be able to do something like Dependency<Components>.getComponentFromTag<IInputComponent>("Input")
so that weapons can just react to the owner's input without regard for where it originated from. The downside is that I don't see a way to have this proposed function actually validate that the component is truly an IInputComponent
, but to me it still seems worth having.
The only current workaround I see is functional, but pretty terrible - have the weapon component do something like components.getComponent<PlayerInput>() ?? components.getComponent<NpcInput>()
. It works but generates import warnings about client/server code in a shared module, and it looks like it should be a compilation error to anyone unfamiliar with how the TS code is transformed to Lua.
Hi, I have a suggestion about your snippets.
May you replace ${1:MyService or MyController} to ${0:$TM_FILENAME_BASE}?
TM_FILENAME_BASE means it will automatically fill up depending on the file name.
Instead of:
import { Controller, OnStart, OnInit } from "@rbxts/flamework";
@Controller({}),
export class MyController implements OnStart, OnInit {
onInit() {
}
onStart() {
}
}
Assume the file name is CoolController.ts
import { Controller, OnStart, OnInit } from "@rbxts/flamework";
@Controller({}),
export class CoolController implements OnStart, OnInit {
onInit() {
}
onStart() {
}
}
It's ok, if you don't want to. I mean, I understand your decisions. 😀
Record<string, number>
generates as an empty object (t.any()
)
Ideally, this should support index signatures for objects with members as well.
I was thinking maybe the instanceGuard property of the component config could return a boolean instead of a t.check so you could do things like return instance.Parent?.IsA("Backpack")
, this would lead to the component not being created if the parent of the instance is not a backpack.
For backwards compatibility, you could have have it return a t.check or a boolean, it wouldn't be too much trouble to handle both scenarios in the implementation.
This seems like it would be pretty easy to do, I could try implementing it if this sounds appealing at all and is approved.
Look into supporting rootDirs
import { OnStart } from "@flamework/core";
import { Component, BaseComponent } from "@flamework/components";
@Component({
defaults: {
attr: 1,
},
})
export class component
extends BaseComponent<{
attr: number;
}>
implements OnStart
{
onStart() {
print(this.attributes.attr); // 1
print(this.instance.GetAttribute("attr")); // nil
}
}
All remote functions have a timeout of 10 seconds
This should be customizable
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.