GithubHelp home page GithubHelp logo

rbxts-flamework / core Goto Github PK

View Code? Open in Web Editor NEW
93.0 5.0 7.0 566 KB

Flamework is an extensible game framework. It requires typescript and offers many useful features.

License: MIT License

TypeScript 100.00%
roblox roblox-ts typescript game-development

core's Introduction

core's People

Contributors

fireboltofdeath avatar osyrisrblx avatar zeroparticle avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

core's Issues

Create integration and unit tests

https://github.com/rbxts-flamework/test-project

Networking

  • Flamework.createEvent
  • connect
  • ServerMethod.fire()
  • ServerMethod.broadcast()
  • ServerMethod.except()
  • ClientMethod()
  • predict

Components

Component Class

  • onAttributeChanged
  • Attributes
  • Instance type
  • config.tag

Components API

  • getComponent
  • addComponent
  • removeComponent

Lifecycle events (Service, Controller, Component)

  • OnStart
  • OnInit
  • OnTick
  • OnPhysics
  • OnRender

Macros

  • Flamework.addPaths
  • Flamework.hash
  • Flamework.implements
  • Flamework.createGuard
  • Flamework.id

Reflection

  • defineMetadata
  • deleteMetadata
  • getOwnMetadata
  • getOwnMetadataKeys
  • getMetadatas
  • getMetadata
  • hasMetadata
  • hasOwnMetadata
  • getMetadataKeys

Bootstrapper

  • Flamework.ignite()

Split project into several packages under @flamework npm org

  • custom flamework project template with configured org
    • probably rbxtsc init game + degit?

@flamework/core

  • bootstrapper
  • services
  • controllers
  • reflection api

@flamework/components

  • BaseComponent
  • Components api

@flamework/networking

  • Networking.createEvent<S, C>()

Implicitly move constructor logic into onStart

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

Mutable attributes on component

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);

Type guard generation fails on Enum unions

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:

  • typescript: 4.4.4
  • roblox-ts: 1.2.3-dev-720146c
  • @flamework/core: 1.0.0-beta.1
  • @flamework/networking: 1.0.0-beta.5
  • rbxts-transformer-flamework: 1.0.0-beta.6

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)

Networking type guard generation treats overloads as unions

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

Great work

厉害呀!很需要这个,希望也能做出点贡献

Ability to add arbitrary instances into DI resolver

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)

Component dependency injection

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

instance guards with children are guranteed to fail on the client no matter what when an instance with a tag is added to workspace

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

Change ${1:any} to ${1:$TM_FILENAME_BASE} in snippets (suggestion)

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. 😀

Ability to retrieve a component by tag

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.

Add support for yielding in component constructors

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()

Modding API

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

  • custom lifecycle events
  • custom decorators
  • custom singletons

Ability to get all Components that extend from a base component (Polymorphism)

(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.

Allow more advanced instance guards in component config

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.

Reflect-metadata-like API

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)

Flamework error

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.

Redesign build info

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)
  • you cannot reuse the output of flamework anywhere, as it has to be under a package, with a 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.

add a second generic to BaseComponent for the instance of the component

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
    }
}

Language service plugin

improves editing experience, e.g

  • importing services/controllers would automatically convert into constructor DI/Dependency macro
  • auto fill types for implemented interfaces
  • hide lifecycle events from access expressions

Predicate for components

A predicate for determining whether an instance should be ignored when attaching components to instances.

Component interdependencies cannot reliably resolve in onStart()

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.

Runtime metadata

Expose metadata about Flamework (e.g config) to Flamework's runtime

Default attributes not set on component

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
	}
}

Hash improvements

  • Option to only hash in production builds (for debugging)
  • Shorthand (instead of Flamework.hash, maybe just $hash()?)
  • Hash an entire enum

Love the idea of hash, let's make it better!

Component onAttributeChanged callbacks report incorrect oldValue on the server

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);
				}
			});
		}
	}
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.