diff --git a/.github/FUNDING.yaml b/.github/FUNDING.yaml new file mode 100644 index 00000000..f8384f2f --- /dev/null +++ b/.github/FUNDING.yaml @@ -0,0 +1 @@ +ko_fi: sleitnick \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e12f5eab..d600029c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -25,29 +25,19 @@ jobs: - name: Build place file run: | rojo build publish.project.json -o Knit.rbxl + rojo build default.project.json -o Knit.rbxm - name: Publish Knit to Roblox shell: bash env: REMODEL_AUTH: ${{ secrets.RBX_AUTH }} run: | remodel run publish.lua - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - draft: false - prerelease: false - - name: Upload Release Asset - id: upload-release-asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Release + uses: softprops/action-gh-release@v1 with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./knit.zip - asset_name: knit.zip - asset_content_type: application/zip + name: Release ${{ github.ref }} + body_path: CHANGELOG.md + fail_on_unmatched_files: true + files: | + knit.zip + Knit.rbxm diff --git a/.gitignore b/.gitignore index 48685b7d..7f2d4c08 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ logo/*.ai logo/discord.png site/ *.rbxlx -*.rbxl \ No newline at end of file +*.rbxl +*.rbxmx +*.rbxm diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..0fcdb8a0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,152 @@ +## 0.0.20-alpha + +- Fixes bug with Timer class +- Updates Janitor +- Removes unnecessary parentheses +- Adds some more Luau types + +## 0.0.19-alpha + +- New Signal implementation +- Remove Thread module in favor of new `task.spawn` and `task.defer` functions +- Add Janitor / Remove Maid +- Add Timer module + +## 0.0.18-alpha + +### Components +- Added optional [`RequiredComponents`](https://sleitnick.github.io/Knit/util/component/#required-components) table for components +- Added [`Observe`](https://sleitnick.github.io/Knit/util/component/#observe) method for components +- Fixed `Added` and `Removed` events not being cleaned up when component class destroyed +- Fixed lifecycle RunService method bindings not being cleaned up properly for future reuse + +### Documentation +- Added [more documentation](https://sleitnick.github.io/Knit/util/component) for components + +### Stability +- Upgraded CI/CD pipeline to use latest packages + +## 0.0.17-alpha + +- Hotfix for TableUtil `Sync`, `Assign`, `Extend`, and `Shuffle` functions to do shallow copies instead of deep copies +- Fix release GitHub action to properly use `"Knit"` as the top-level directory name within the zipped file +- Fix documentation to properly use user preference theme (light/dark) + +## 0.0.16-alpha + +**[BR]** = Breaking Change +- Project directory restructure +- Can now include Knit as a Git submodule and reference the default rojo project to sync in (see below) +- Added unit tests for Knit-specific utility modules +- Added simple integration tests +- TableUtil fixes, additions, and improvements: + - **[BR]** All functions (except `FastRemove` and `FastRemoveFirstValue`) no longer mutate table + - Fix `Filter` bug introduced in v0.0.15-alpha + - Fix behavior of `Extend` to extend arrays and not dictionaries (use `Assign` to extend a dictionary) + - Add optional RNG override parameter for `Shuffle` + - Add `Flat`, `FlatMap`, `Keys`, `Find`, `Every`, and `Some` functions + - Add documentation page for TableUtil +- Simplify `Knit.OnStart()` internally to use `Promise.FromEvent` +- Update Rojo version used by CI/CD pipeline +- Fix broken links in documentation pages + +## 0.0.15-alpha + +- Memory leak fixed with Streamable when instance was immediately available +- `Knit.GetService(serviceName)` added to server-side Knit +- Minor improvements to TableUtil +- Util documentation split across multiple pages + +## 0.0.14-alpha + +- Fix Signal leak when firing with no connections +- Change `._instance` to `.Instance` in Component +- Components will use attributes to store unique ID instead of StringValue +- Add `Signal.Proxy` constructor to wrap built-in RBXScriptSignals +- Add `Maid:GivePromise` method +- Allow dictionary tables in `StreamableUtil.Compound` observers list + +**Note breaking changes from above:** +- When upgrading, make sure to change `._instance` field accessors to `.Instance` for components +- `ServerID` StringValue for components has been switched to use attributes: `instance:GetAttribute("ComponentServerId")` + +## 0.0.13-alpha + +- `Component:WaitFor` has been rewritten to utilize built-in promise features better, which also eliminated an existing event connection leak. +- `Streamable` and `StreamableUtil` modules added to easily manage parts that may stream in & out during runtime when using [`StreamingEnabled`](https://developer.roblox.com/en-us/api-reference/property/Workspace/StreamingEnabled). +- Documentation improvements. + +## 0.0.12-alpha + +- Added new 'Add' functions to automatically load all modules in a folder. This is useful for quickly loading a bunch of service or controller modules: + - `KnitServer.AddServices(folder: Instance)` + - `KnitServer.AddServicesDeep(folder: Instance)` + - `KnitClient.AddControllers(folder: Instance)` + - `KnitClient.AddControllersDeep(folder: Instance)` +- Split up remotes to server/client versions: + - `RemoteEvent` -> `RemoteSignal` and `ClientRemoteSignal` + - `RemoteProperty` -> `RemoteProperty` and `ClientRemoteProperty` +- Knit module isn't required to live in ReplicatedStorage now +- Added `EnumList` class which wraps `Symbol`s to create pseudo-enums +- Added style guide in documentation + +## 0.0.11-alpha + +- Documentation fixes and additions +- Better table support for `RemoteProperty` class +- Fixes and additions to `Option` class +- Optional behavior argument for `Thread.DelayRepeat` + - **Note:** If using var-args list for `DelayRepeat`, this is a breaking change. `DelayRepeat`'s third argument must be the behavior (`Thread.DelayRepeatBehavior.Delayed` or `Thread.DelayRepeatBehavior.Immediate`). +- Added `Symbol` class + +## 0.0.10-alpha + +- Switch default branch from `master` to `main` +- `Component:WaitFor` first arg can now be a name or instance +- `Init` for individual components is called after a heartbeat, which helps allow components to get other components without race conditions when `Component.Auto` is used. + +## 0.0.9-alpha + +- Fixed issue where remote objects were parented before services completed initialization. This created a possible race condition between services initializing and clients loading Knit. + +## 0.0.8-alpha + +- Added `Option` class for creating optionals. +- Added serialization/deserialization automatic flow for RemoteEvents and RemoteFunctions. +- Upgraded `Promise` to v3.0.1. + +## 0.0.7-alpha + +- Added a few tests (very few so far) +- Added PascalCase methods to Promise module +- Components will only trigger for instances that are descendants of Players or Workspace by default +- GitHub workflow to auto-publish the [Knit](https://www.roblox.com/library/5530714855/Knit) module to Roblox. + +## 0.0.6-alpha + +- Add more functionality to Component module + +## 0.0.5-alpha + +- Added Component class, which allows developers to bind component classes to in-game instances using the CollectionService tags +- Renamed `Event` to `Signal` + +## 0.0.4-alpha + +- Ability to use tables within RemoteProperty object +- RemoteProperty now has `property:Replicate()` method server-side that must be called when a table value is changed ([see doc](https://sleitnick.github.io/Knit/util/#remoteproperty)) + +## 0.0.3-alpha + +- Add more documentation +- Inject `Player` field into `KnitClient` + +## 0.0.2-alpha + +- Add `Knit.OnStart()` to capture when Knit starts +- Add RemoteEvent and RemoteProperty +- Add documentation + +## 0.0.1-alpha + +- Initial release \ No newline at end of file diff --git a/default.project.json b/default.project.json index abfccab2..ddad7887 100644 --- a/default.project.json +++ b/default.project.json @@ -3,4 +3,4 @@ "tree": { "$path": "src" } -} \ No newline at end of file +} diff --git a/docs.sh b/docs.sh index 9b3ccec9..41abf215 100644 --- a/docs.sh +++ b/docs.sh @@ -47,4 +47,4 @@ else echo "Error: Command not recognized" help exit 1 -fi \ No newline at end of file +fi diff --git a/foreman.toml b/foreman.toml index e9c6f86e..ac244095 100644 --- a/foreman.toml +++ b/foreman.toml @@ -1,5 +1,4 @@ [tools] -# Install latest selene selene = { source = "Kampfkarren/selene", version = "x" } rojo = { source = "rojo-rbx/rojo", version = "6.1.0" } remodel = { source = "rojo-rbx/remodel", version = "0.8.1"} diff --git a/publish.project.json b/publish.project.json index cbb2d7bf..669fcaef 100644 --- a/publish.project.json +++ b/publish.project.json @@ -9,4 +9,4 @@ } } } -} \ No newline at end of file +} diff --git a/remodel.toml b/remodel.toml index fab353d8..cd620872 100644 --- a/remodel.toml +++ b/remodel.toml @@ -91,4 +91,4 @@ type = "string" [[json.toString.args]] required = true -type = "any" \ No newline at end of file +type = "any" diff --git a/roblox.toml b/roblox.toml index 394f8406..7e7b9495 100644 --- a/roblox.toml +++ b/roblox.toml @@ -1,4 +1,4 @@ -# This file was @generated by generate-roblox-std at 2021-07-28 14:57:44.481098300 -04:00 +# This file was @generated by generate-roblox-std at 2021-08-12 13:28:30.360077200 -04:00 [selene] base = "lua51" name = "roblox" @@ -1117,7 +1117,7 @@ type = "any" method = true [[selene.structs.DataModel.GetService.args]] -type = ["ABTestService", "AdService", "AnalyticsService", "AppUpdateService", "AssetCounterService", "AssetDeliveryProxy", "AssetImportService", "AssetManagerService", "AssetService", "AvatarEditorService", "AvatarImportService", "BadgeService", "CoreGui", "StarterGui", "BrowserService", "BulkImportService", "CacheableContentProvider", "MeshContentProvider", "SolidModelContentProvider", "CalloutService", "ChangeHistoryService", "Chat", "ClusterPacketCache", "CollectionService", "CommandService", "ContentProvider", "ContextActionService", "ControllerService", "CookiesService", "CorePackages", "CoreScriptSyncService", "DataStoreService", "Debris", "DebuggerConnectionManager", "DebuggerManager", "DraftsService", "DraggerService", "EventIngestService", "FlagStandService", "FlyweightService", "CSGDictionaryService", "NonReplicatedCSGDictionaryService", "FriendService", "GamePassService", "GamepadService", "Geometry", "GoogleAnalyticsConfiguration", "GroupService", "GuiService", "GuidRegistryService", "HapticService", "HeightmapImporterService", "Hopper", "HttpRbxApiService", "HttpService", "ILegacyStudioBridge", "LegacyStudioBridge", "IXPService", "IncrementalPatchBuilder", "InsertService", "InternalContainer", "JointsService", "KeyboardService", "KeyframeSequenceProvider", "LanguageService", "Lighting", "LocalStorageService", "AppStorageService", "UserStorageService", "LocalizationService", "LogService", "LoginService", "LuaWebService", "MarketplaceService", "MemStorageService", "MemoryStoreService", "MessagingService", "MouseService", "NetworkClient", "NetworkServer", "NetworkSettings", "NotificationService", "Workspace", "PackageService", "PathfindingService", "PermissionsService", "PhysicsService", "PlayerEmulatorService", "Players", "PluginDebugService", "PluginGuiService", "PluginPolicyService", "PointsService", "PolicyService", "ProximityPromptService", "PublishService", "RbxAnalyticsService", "RemoteDebuggerServer", "RenderSettings", "ReplicatedFirst", "ReplicatedScriptService", "ReplicatedStorage", "RobloxPluginGuiService", "RobloxReplicatedStorage", "RunService", "RuntimeScriptService", "ScriptContext", "ScriptService", "Selection", "ServerScriptService", "ServerStorage", "SessionService", "SocialService", "SoundService", "SpawnerService", "StarterPack", "StarterPlayer", "Stats", "StopWatchReporter", "Studio", "StudioData", "StudioDeviceEmulatorService", "StudioService", "TaskScheduler", "Teams", "TeleportService", "TestService", "TextService", "ThirdPartyUserService", "TimerService", "ToastNotificationService", "TouchInputService", "TracerService", "TweenService", "UGCValidationService", "UnvalidatedAssetService", "UserInputService", "UserService", "VRService", "VersionControlService", "VirtualInputManager", "VirtualUser", "Visit"] +type = ["ABTestService", "AdService", "AnalyticsService", "AppUpdateService", "AssetCounterService", "AssetDeliveryProxy", "AssetImportService", "AssetManagerService", "AssetService", "AvatarEditorService", "AvatarImportService", "BadgeService", "CoreGui", "StarterGui", "BrowserService", "BulkImportService", "CacheableContentProvider", "MeshContentProvider", "SolidModelContentProvider", "CalloutService", "ChangeHistoryService", "Chat", "ClusterPacketCache", "CollectionService", "CommandService", "ContentProvider", "ContextActionService", "ControllerService", "CookiesService", "CorePackages", "CoreScriptSyncService", "DataStoreService", "Debris", "DebuggerConnectionManager", "DebuggerManager", "DraftsService", "DraggerService", "EventIngestService", "FlagStandService", "FlyweightService", "CSGDictionaryService", "NonReplicatedCSGDictionaryService", "FriendService", "GamePassService", "GamepadService", "Geometry", "GoogleAnalyticsConfiguration", "GroupService", "GuiService", "GuidRegistryService", "HapticService", "HeightmapImporterService", "Hopper", "HttpRbxApiService", "HttpService", "ILegacyStudioBridge", "LegacyStudioBridge", "IXPService", "IncrementalPatchBuilder", "InsertService", "InternalContainer", "JointsService", "KeyboardService", "KeyframeSequenceProvider", "LanguageService", "Lighting", "LocalStorageService", "AppStorageService", "UserStorageService", "LocalizationService", "LogService", "LoginService", "LuaWebService", "MarketplaceService", "MemStorageService", "MemoryStoreService", "MessagingService", "MouseService", "NetworkClient", "NetworkServer", "NetworkSettings", "NotificationService", "Workspace", "PackageService", "PathfindingService", "PermissionsService", "PhysicsService", "PlayerEmulatorService", "Players", "PluginDebugService", "PluginGuiService", "PluginPolicyService", "PointsService", "PolicyService", "ProximityPromptService", "PublishService", "RbxAnalyticsService", "RemoteDebuggerServer", "RenderSettings", "ReplicatedFirst", "ReplicatedScriptService", "ReplicatedStorage", "RobloxPluginGuiService", "RobloxReplicatedStorage", "RunService", "RuntimeScriptService", "ScriptContext", "ScriptService", "Selection", "ServerScriptService", "ServerStorage", "SessionService", "SocialService", "SoundService", "SpawnerService", "StarterPack", "StarterPlayer", "Stats", "StopWatchReporter", "Studio", "StudioData", "StudioDeviceEmulatorService", "StudioService", "TaskScheduler", "Teams", "TeleportService", "TestService", "TextService", "ThirdPartyUserService", "TimerService", "ToastNotificationService", "TouchInputService", "TracerService", "TweenService", "UGCValidationService", "UnvalidatedAssetService", "UserInputService", "UserService", "VRService", "VersionControlService", "VirtualInputManager", "VirtualUser", "Visit", "VoiceChatService"] [selene.structs.DataModel.GraphicsQualityChangeRequest] struct = "Event" @@ -7475,6 +7475,27 @@ struct = "EnumItem" [Enum.MeshPartHeadsAndAccessories.GetEnumItems] method = true args = [] +[Enum.MeshScaleUnit.CM] +struct = "EnumItem" + +[Enum.MeshScaleUnit.Foot] +struct = "EnumItem" + +[Enum.MeshScaleUnit.GetEnumItems] +method = true +args = [] + +[Enum.MeshScaleUnit.Inch] +struct = "EnumItem" + +[Enum.MeshScaleUnit.MM] +struct = "EnumItem" + +[Enum.MeshScaleUnit.Meter] +struct = "EnumItem" + +[Enum.MeshScaleUnit.Stud] +struct = "EnumItem" [Enum.MeshType.Brick] struct = "EnumItem" @@ -8153,6 +8174,15 @@ struct = "EnumItem" [Enum.RenderingTestComparisonMethod.psnr] struct = "EnumItem" +[Enum.ResamplerMode.Default] +struct = "EnumItem" + +[Enum.ResamplerMode.GetEnumItems] +method = true +args = [] + +[Enum.ResamplerMode.Pixelated] +struct = "EnumItem" [Enum.ReturnKeyType.Default] struct = "EnumItem" @@ -9749,6 +9779,18 @@ struct = "EnumItem" [Enum.VRTouchpadMode.VirtualThumbstick] struct = "EnumItem" +[Enum.VelocityConstraintMode.GetEnumItems] +method = true +args = [] + +[Enum.VelocityConstraintMode.Line] +struct = "EnumItem" + +[Enum.VelocityConstraintMode.Plane] +struct = "EnumItem" + +[Enum.VelocityConstraintMode.Vector] +struct = "EnumItem" [Enum.VerticalAlignment.Bottom] struct = "EnumItem" @@ -9815,6 +9857,30 @@ struct = "EnumItem" [Enum.VirtualInputMode.Recording] struct = "EnumItem" +[Enum.VoiceChatState.Ended] +struct = "EnumItem" + +[Enum.VoiceChatState.Failed] +struct = "EnumItem" + +[Enum.VoiceChatState.GetEnumItems] +method = true +args = [] + +[Enum.VoiceChatState.Idle] +struct = "EnumItem" + +[Enum.VoiceChatState.Joined] +struct = "EnumItem" + +[Enum.VoiceChatState.Joining] +struct = "EnumItem" + +[Enum.VoiceChatState.JoiningRetry] +struct = "EnumItem" + +[Enum.VoiceChatState.Leaving] +struct = "EnumItem" [Enum.WaterDirection.GetEnumItems] method = true args = [] @@ -9917,7 +9983,7 @@ struct = "EnumItem" [[Faces.new.args]] type = "..." [[Instance.new.args]] -type = ["Accoutrement", "Accessory", "Hat", "AdvancedDragger", "AnalyticsService", "Animation", "AnimationController", "Animator", "Atmosphere", "Attachment", "Bone", "Backpack", "HopperBin", "Tool", "Flag", "WrapLayer", "WrapTarget", "Beam", "BindableEvent", "BindableFunction", "BodyAngularVelocity", "BodyForce", "BodyGyro", "BodyPosition", "BodyThrust", "BodyVelocity", "RocketPropulsion", "Camera", "BodyColors", "CharacterMesh", "Pants", "Shirt", "ShirtGraphic", "Skin", "ClickDetector", "Clouds", "Configuration", "AlignOrientation", "AlignPosition", "AngularVelocity", "BallSocketConstraint", "HingeConstraint", "LineForce", "RodConstraint", "RopeConstraint", "CylindricalConstraint", "PrismaticConstraint", "SpringConstraint", "Torque", "TorsionSpringConstraint", "UniversalConstraint", "VectorForce", "HumanoidController", "SkateboardController", "VehicleController", "CustomEvent", "CustomEventReceiver", "BlockMesh", "CylinderMesh", "FileMesh", "SpecialMesh", "DataStoreIncrementOptions", "DataStoreSetOptions", "DebuggerWatch", "Dialog", "DialogChoice", "Dragger", "Explosion", "Expression", "FaceControls", "Decal", "Texture", "Hole", "MotorFeature", "Fire", "FlyweightService", "CSGDictionaryService", "NonReplicatedCSGDictionaryService", "Folder", "ForceField", "FunctionalTest", "GetDataStoreOptions", "Frame", "ImageButton", "TextButton", "ImageLabel", "TextLabel", "ScrollingFrame", "TextBox", "VideoFrame", "ViewportFrame", "BillboardGui", "ScreenGui", "GuiMain", "SurfaceGui", "FloorWire", "SelectionBox", "BoxHandleAdornment", "ConeHandleAdornment", "CylinderHandleAdornment", "ImageHandleAdornment", "LineHandleAdornment", "SphereHandleAdornment", "ParabolaAdornment", "SelectionSphere", "ArcHandles", "Handles", "SurfaceSelection", "SelectionPartLasso", "SelectionPointLasso", "HeightmapImporterService", "Humanoid", "HumanoidDescription", "RotateP", "RotateV", "Glue", "ManualGlue", "ManualWeld", "Motor", "Motor6D", "Rotate", "Snap", "VelocityMotor", "Weld", "Keyframe", "KeyframeMarker", "KeyframeSequence", "PointLight", "SpotLight", "SurfaceLight", "LocalizationTable", "Script", "LocalScript", "ModuleScript", "MemoryStoreService", "Message", "Hint", "NoCollisionConstraint", "CornerWedgePart", "Part", "FlagStand", "Seat", "SkateboardPlatform", "SpawnLocation", "WedgePart", "MeshPart", "PartOperation", "NegateOperation", "UnionOperation", "TrussPart", "VehicleSeat", "Model", "Actor", "WorldModel", "PartOperationAsset", "ParticleEmitter", "Player", "PluginAction", "NumberPose", "Pose", "BloomEffect", "BlurEffect", "ColorCorrectionEffect", "DepthOfFieldEffect", "SunRaysEffect", "ProximityPrompt", "ProximityPromptService", "ReflectionMetadata", "ReflectionMetadataCallbacks", "ReflectionMetadataClasses", "ReflectionMetadataEnums", "ReflectionMetadataEvents", "ReflectionMetadataFunctions", "ReflectionMetadataClass", "ReflectionMetadataEnum", "ReflectionMetadataEnumItem", "ReflectionMetadataMember", "ReflectionMetadataProperties", "ReflectionMetadataYieldFunctions", "RemoteEvent", "RemoteFunction", "RenderingTest", "Sky", "Smoke", "Sound", "ChorusSoundEffect", "CompressorSoundEffect", "DistortionSoundEffect", "EchoSoundEffect", "EqualizerSoundEffect", "FlangeSoundEffect", "PitchShiftSoundEffect", "ReverbSoundEffect", "TremoloSoundEffect", "SoundGroup", "Sparkles", "Speaker", "StandalonePluginScripts", "StarterGear", "SurfaceAppearance", "Team", "TeleportOptions", "TerrainRegion", "TestService", "Trail", "Tween", "UIAspectRatioConstraint", "UISizeConstraint", "UITextSizeConstraint", "UICorner", "UIGradient", "UIGridLayout", "UIListLayout", "UIPageLayout", "UITableLayout", "UIPadding", "UIScale", "UIStroke", "BinaryStringValue", "BoolValue", "BrickColorValue", "CFrameValue", "Color3Value", "DoubleConstrainedValue", "IntConstrainedValue", "IntValue", "NumberValue", "ObjectValue", "RayValue", "StringValue", "Vector3Value", "VirtualInputManager", "WeldConstraint"] +type = ["Accoutrement", "Accessory", "Hat", "AdvancedDragger", "AnalyticsService", "Animation", "AnimationController", "Animator", "Atmosphere", "Attachment", "Bone", "Backpack", "HopperBin", "Tool", "Flag", "WrapLayer", "WrapTarget", "Beam", "BindableEvent", "BindableFunction", "BodyAngularVelocity", "BodyForce", "BodyGyro", "BodyPosition", "BodyThrust", "BodyVelocity", "RocketPropulsion", "Camera", "BodyColors", "CharacterMesh", "Pants", "Shirt", "ShirtGraphic", "Skin", "ClickDetector", "Clouds", "Configuration", "AlignOrientation", "AlignPosition", "AngularVelocity", "BallSocketConstraint", "HingeConstraint", "LineForce", "LinearVelocityConstraint", "RodConstraint", "RopeConstraint", "CylindricalConstraint", "PrismaticConstraint", "SpringConstraint", "Torque", "TorsionSpringConstraint", "UniversalConstraint", "VectorForce", "HumanoidController", "SkateboardController", "VehicleController", "CustomEvent", "CustomEventReceiver", "BlockMesh", "CylinderMesh", "FileMesh", "SpecialMesh", "DataStoreIncrementOptions", "DataStoreOptions", "DataStoreSetOptions", "DebuggerWatch", "Dialog", "DialogChoice", "Dragger", "Explosion", "Expression", "FaceControls", "Decal", "Texture", "Hole", "MotorFeature", "Fire", "FlyweightService", "CSGDictionaryService", "NonReplicatedCSGDictionaryService", "Folder", "ForceField", "FunctionalTest", "Frame", "ImageButton", "TextButton", "ImageLabel", "TextLabel", "ScrollingFrame", "TextBox", "VideoFrame", "ViewportFrame", "BillboardGui", "ScreenGui", "GuiMain", "SurfaceGui", "FloorWire", "SelectionBox", "BoxHandleAdornment", "ConeHandleAdornment", "CylinderHandleAdornment", "ImageHandleAdornment", "LineHandleAdornment", "SphereHandleAdornment", "ParabolaAdornment", "SelectionSphere", "ArcHandles", "Handles", "SurfaceSelection", "SelectionPartLasso", "SelectionPointLasso", "HeightmapImporterService", "Humanoid", "HumanoidDescription", "RotateP", "RotateV", "Glue", "ManualGlue", "ManualWeld", "Motor", "Motor6D", "Rotate", "Snap", "VelocityMotor", "Weld", "Keyframe", "KeyframeMarker", "KeyframeSequence", "PointLight", "SpotLight", "SurfaceLight", "LocalizationTable", "Script", "LocalScript", "ModuleScript", "MemoryStoreService", "Message", "Hint", "NoCollisionConstraint", "CornerWedgePart", "Part", "FlagStand", "Seat", "SkateboardPlatform", "SpawnLocation", "WedgePart", "MeshPart", "PartOperation", "NegateOperation", "UnionOperation", "TrussPart", "VehicleSeat", "Model", "Actor", "WorldModel", "PartOperationAsset", "ParticleEmitter", "PathfindingModifier", "Player", "PluginAction", "NumberPose", "Pose", "BloomEffect", "BlurEffect", "ColorCorrectionEffect", "DepthOfFieldEffect", "SunRaysEffect", "ProximityPrompt", "ProximityPromptService", "ReflectionMetadata", "ReflectionMetadataCallbacks", "ReflectionMetadataClasses", "ReflectionMetadataEnums", "ReflectionMetadataEvents", "ReflectionMetadataFunctions", "ReflectionMetadataClass", "ReflectionMetadataEnum", "ReflectionMetadataEnumItem", "ReflectionMetadataMember", "ReflectionMetadataProperties", "ReflectionMetadataYieldFunctions", "RemoteEvent", "RemoteFunction", "RenderingTest", "Sky", "Smoke", "Sound", "ChorusSoundEffect", "CompressorSoundEffect", "DistortionSoundEffect", "EchoSoundEffect", "EqualizerSoundEffect", "FlangeSoundEffect", "PitchShiftSoundEffect", "ReverbSoundEffect", "TremoloSoundEffect", "SoundGroup", "Sparkles", "Speaker", "StandalonePluginScripts", "StarterGear", "SurfaceAppearance", "Team", "TeleportOptions", "TerrainRegion", "TestService", "Trail", "Tween", "UIAspectRatioConstraint", "UISizeConstraint", "UITextSizeConstraint", "UICorner", "UIGradient", "UIGridLayout", "UIListLayout", "UIPageLayout", "UITableLayout", "UIPadding", "UIScale", "UIStroke", "BinaryStringValue", "BoolValue", "BrickColorValue", "CFrameValue", "Color3Value", "DoubleConstrainedValue", "IntConstrainedValue", "IntValue", "NumberValue", "ObjectValue", "RayValue", "StringValue", "Vector3Value", "VirtualInputManager", "WeldConstraint"] [[NumberRange.new.args]] type = "number" @@ -10397,6 +10463,37 @@ type = "number" [[table.unpack.args]] required = false type = "number" +[[task.defer.args]] +type = "function" + +[[task.defer.args]] +required = false +type = "..." +[[task.delay.args]] +required = false +type = "number" + +[[task.delay.args]] +type = "function" + +[[task.delay.args]] +required = false +type = "..." + +[task.desynchronize] +args = [] +[[task.spawn.args]] +type = "function" + +[[task.spawn.args]] +required = false +type = "..." + +[task.synchronize] +args = [] +[[task.wait.args]] +required = false +type = "number" [tick] args = [] diff --git a/robloxfuture.toml b/robloxfuture.toml deleted file mode 100644 index 950ad40d..00000000 --- a/robloxfuture.toml +++ /dev/null @@ -1,15 +0,0 @@ -[[task.spawn.args]] -type = "function" -[[task.spawn.args]] -required = false -type = "..." - -[[task.defer.args]] -type = "function" -[[task.defer.args]] -required = false -type = "..." - -[[task.wait.args]] -required = false -type = "number" diff --git a/selene.toml b/selene.toml index ea7e11b9..a1dddbd2 100644 --- a/selene.toml +++ b/selene.toml @@ -1,9 +1,8 @@ -std = "roblox+remodel+testez+robloxfuture" +std = "roblox+remodel+testez" [rules] -parenthese_conditions = "allow" multiple_statements = "allow" global_usage = "allow" [config] -unused_variable = {allow_unused_self = true} \ No newline at end of file +unused_variable = {allow_unused_self = true} diff --git a/src/KnitClient.lua b/src/KnitClient.lua index 5972b39e..12068979 100644 --- a/src/KnitClient.lua +++ b/src/KnitClient.lua @@ -1,3 +1,5 @@ +--!strict + --[[ Knit.CreateController(controller): Controller @@ -11,12 +13,27 @@ --]] +type ControllerDef = { + Name: string, + [any]: any, +} + +type Controller = { + Name: string, + [any]: any, +} + +type Service = { + [any]: any, +} + + local KnitClient = {} -KnitClient.Version = script.Parent.Version.Value +KnitClient.Version = script.Parent:WaitForChild("Version").Value KnitClient.Player = game:GetService("Players").LocalPlayer -KnitClient.Controllers = {} -KnitClient.Util = script.Parent.Util +KnitClient.Controllers = {} :: {[string]: Controller} +KnitClient.Util = script.Parent:WaitForChild("Util") local Promise = require(KnitClient.Util.Promise) local Loader = require(KnitClient.Util.Loader) @@ -25,7 +42,7 @@ local ClientRemoteSignal = require(KnitClient.Util.Remote.ClientRemoteSignal) local ClientRemoteProperty = require(KnitClient.Util.Remote.ClientRemoteProperty) local TableUtil = require(KnitClient.Util.TableUtil) -local services = {} +local services: {[string]: Service} = {} local servicesFolder = script.Parent:WaitForChild("Services") local started = false @@ -33,33 +50,38 @@ local startedComplete = false local onStartedComplete = Instance.new("BindableEvent") -local function BuildService(serviceName, folder) +local function BuildService(serviceName: string, folder: Instance): Service local service = {} - if (folder:FindFirstChild("RF")) then - for _,rf in ipairs(folder.RF:GetChildren()) do - if (rf:IsA("RemoteFunction")) then - service[rf.Name] = function(_self, ...) + local rfFolder = folder:FindFirstChild("RF") + local reFolder = folder:FindFirstChild("RE") + local rpFolder = folder:FindFirstChild("RP") + if rfFolder then + for _,rf in ipairs(rfFolder:GetChildren()) do + if rf:IsA("RemoteFunction") then + local function StandardRemote(_self, ...) return Ser.DeserializeArgsAndUnpack(rf:InvokeServer(Ser.SerializeArgsAndUnpack(...))) end - service[rf.Name .. "Promise"] = function(_self, ...) + local function PromiseRemote(_self, ...) local args = Ser.SerializeArgs(...) return Promise.new(function(resolve) resolve(Ser.DeserializeArgsAndUnpack(rf:InvokeServer(table.unpack(args, 1, args.n)))) end) end + service[rf.Name] = StandardRemote + service[rf.Name .. "Promise"] = PromiseRemote end end end - if (folder:FindFirstChild("RE")) then - for _,re in ipairs(folder.RE:GetChildren()) do - if (re:IsA("RemoteEvent")) then + if reFolder then + for _,re in ipairs(reFolder:GetChildren()) do + if re:IsA("RemoteEvent") then service[re.Name] = ClientRemoteSignal.new(re) end end end - if (folder:FindFirstChild("RP")) then - for _,rp in ipairs(folder.RP:GetChildren()) do - if (rp:IsA("ValueBase") or rp:IsA("RemoteEvent")) then + if rpFolder then + for _,rp in ipairs(rpFolder:GetChildren()) do + if rp:IsA("ValueBase") or rp:IsA("RemoteEvent") then service[rp.Name] = ClientRemoteProperty.new(rp) end end @@ -69,12 +91,18 @@ local function BuildService(serviceName, folder) end -function KnitClient.CreateController(controller) - assert(type(controller) == "table", "Controller must be a table; got " .. type(controller)) - assert(type(controller.Name) == "string", "Controller.Name must be a string; got " .. type(controller.Name)) - assert(#controller.Name > 0, "Controller.Name must be a non-empty string") - assert(KnitClient.Controllers[controller.Name] == nil, "Controller \"" .. controller.Name .. "\" already exists") - controller = TableUtil.Assign(controller, { +local function DoesControllerExist(controllerName: string): boolean + local controller: Controller? = KnitClient.Controllers[controllerName] + return controller ~= nil +end + + +function KnitClient.CreateController(controllerDef: ControllerDef): Controller + assert(type(controllerDef) == "table", "Controller must be a table; got " .. type(controllerDef)) + assert(type(controllerDef.Name) == "string", "Controller.Name must be a string; got " .. type(controllerDef.Name)) + assert(#controllerDef.Name > 0, "Controller.Name must be a non-empty string") + assert(not DoesControllerExist(controllerDef.Name), "Controller \"" .. controllerDef.Name .. "\" already exists") + local controller: Controller = TableUtil.Assign(controllerDef, { _knit_is_controller = true; }) KnitClient.Controllers[controller.Name] = controller @@ -82,32 +110,32 @@ function KnitClient.CreateController(controller) end -function KnitClient.AddControllers(folder) +function KnitClient.AddControllers(folder: Instance): {any} return Loader.LoadChildren(folder) end -function KnitClient.AddControllersDeep(folder) +function KnitClient.AddControllersDeep(folder: Instance): {any} return Loader.LoadDescendants(folder) end -function KnitClient.GetService(serviceName) +function KnitClient.GetService(serviceName: string): Service assert(type(serviceName) == "string", "ServiceName must be a string; got " .. type(serviceName)) - local folder = servicesFolder:FindFirstChild(serviceName) + local folder: Instance? = servicesFolder:FindFirstChild(serviceName) assert(folder ~= nil, "Could not find service \"" .. serviceName .. "\"") - return services[serviceName] or BuildService(serviceName, folder) + return services[serviceName] or BuildService(serviceName, folder :: Instance) end -function KnitClient.GetController(controllerName) +function KnitClient.GetController(controllerName: string): Controller? return KnitClient.Controllers[controllerName] end function KnitClient.Start() - if (started) then + if started then return Promise.Reject("Knit already started") end @@ -120,7 +148,7 @@ function KnitClient.Start() -- Init: local promisesStartControllers = {} for _,controller in pairs(controllers) do - if (type(controller.KnitInit) == "function") then + if type(controller.KnitInit) == "function" then table.insert(promisesStartControllers, Promise.new(function(r) controller:KnitInit() r() @@ -134,7 +162,7 @@ function KnitClient.Start() -- Start: for _,controller in pairs(controllers) do - if (type(controller.KnitStart) == "function") then + if type(controller.KnitStart) == "function" then task.spawn(controller.KnitStart, controller) end end @@ -152,7 +180,7 @@ end function KnitClient.OnStart() - if (startedComplete) then + if startedComplete then return Promise.Resolve() else return Promise.FromEvent(onStartedComplete.Event) diff --git a/src/KnitServer.lua b/src/KnitServer.lua index 6efae1bf..017966eb 100644 --- a/src/KnitServer.lua +++ b/src/KnitServer.lua @@ -1,3 +1,5 @@ +--!strict + --[[ Knit.CreateService(service): Service @@ -9,10 +11,33 @@ --]] +type ServiceDef = { + Name: string, + Client: {[any]: any}?, + [any]: any, +} + +type Service = { + Name: string, + Client: ServiceClient, + _knit_is_service: boolean, + _knit_rf: {}, + _knit_re: {}, + _knit_rp: {}, + _knit_rep_folder: Instance, + [any]: any, +} + +type ServiceClient = { + Server: Service, + [any]: any, +} + + local KnitServer = {} KnitServer.Version = script.Parent.Version.Value -KnitServer.Services = {} +KnitServer.Services = {} :: {[string]: Service} KnitServer.Util = script.Parent.Util @@ -32,16 +57,16 @@ local startedComplete = false local onStartedComplete = Instance.new("BindableEvent") -local function CreateRepFolder(serviceName) +local function CreateRepFolder(serviceName: string): Instance local folder = Instance.new("Folder") folder.Name = serviceName return folder end -local function GetFolderOrCreate(parent, name) +local function GetFolderOrCreate(parent: Instance, name: string): Instance local f = parent:FindFirstChild(name) - if (not f) then + if not f then f = Instance.new("Folder") f.Name = name f.Parent = parent @@ -50,45 +75,51 @@ local function GetFolderOrCreate(parent, name) end -local function AddToRepFolder(service, remoteObj, folderOverride) - if (folderOverride) then +local function AddToRepFolder(service: Service, remoteObj: Instance, folderOverride: string?) + if folderOverride then remoteObj.Parent = GetFolderOrCreate(service._knit_rep_folder, folderOverride) - elseif (remoteObj:IsA("RemoteFunction")) then + elseif remoteObj:IsA("RemoteFunction") then remoteObj.Parent = GetFolderOrCreate(service._knit_rep_folder, "RF") - elseif (remoteObj:IsA("RemoteEvent")) then + elseif remoteObj:IsA("RemoteEvent") then remoteObj.Parent = GetFolderOrCreate(service._knit_rep_folder, "RE") - elseif (remoteObj:IsA("ValueBase")) then + elseif remoteObj:IsA("ValueBase") then remoteObj.Parent = GetFolderOrCreate(service._knit_rep_folder, "RP") else error("Invalid rep object: " .. remoteObj.ClassName) end - if (not service._knit_rep_folder.Parent) then + if not service._knit_rep_folder.Parent then service._knit_rep_folder.Parent = knitRepServiceFolder end end -function KnitServer.IsService(object) +local function DoesServiceExist(serviceName: string): boolean + local service: Service? = KnitServer.Services[serviceName] + return service ~= nil +end + + +function KnitServer.IsService(object: any): boolean return type(object) == "table" and object._knit_is_service == true end -function KnitServer.CreateService(service) - assert(type(service) == "table", "Service must be a table; got " .. type(service)) - assert(type(service.Name) == "string", "Service.Name must be a string; got " .. type(service.Name)) - assert(#service.Name > 0, "Service.Name must be a non-empty string") - assert(KnitServer.Services[service.Name] == nil, "Service \"" .. service.Name .. "\" already exists") - service = TableUtil.Assign(service, { +function KnitServer.CreateService(serviceDef: ServiceDef): Service + assert(type(serviceDef) == "table", "Service must be a table; got " .. type(serviceDef)) + assert(type(serviceDef.Name) == "string", "Service.Name must be a string; got " .. type(serviceDef.Name)) + assert(#serviceDef.Name > 0, "Service.Name must be a non-empty string") + assert(not DoesServiceExist(serviceDef.Name), "Service \"" .. serviceDef.Name .. "\" already exists") + local service: Service = TableUtil.Assign(serviceDef, { _knit_is_service = true; _knit_rf = {}; _knit_re = {}; _knit_rp = {}; - _knit_rep_folder = CreateRepFolder(service.Name); + _knit_rep_folder = CreateRepFolder(serviceDef.Name); }) - if (type(service.Client) ~= "table") then + if type(service.Client) ~= "table" then service.Client = {Server = service} else - if (service.Client.Server ~= service) then + if service.Client.Server ~= service then service.Client.Server = service end end @@ -97,23 +128,23 @@ function KnitServer.CreateService(service) end -function KnitServer.AddServices(folder) +function KnitServer.AddServices(folder: Instance): {any} return Loader.LoadChildren(folder) end -function KnitServer.AddServicesDeep(folder) +function KnitServer.AddServicesDeep(folder: Instance): {any} return Loader.LoadDescendants(folder) end -function KnitServer.GetService(serviceName) +function KnitServer.GetService(serviceName: string): Service assert(type(serviceName) == "string", "ServiceName must be a string; got " .. type(serviceName)) - return assert(KnitServer.Services[serviceName], "Could not find service \"" .. serviceName .. "\"") + return assert(KnitServer.Services[serviceName], "Could not find service \"" .. serviceName .. "\"") :: Service end -function KnitServer.BindRemoteEvent(service, eventName, remoteEvent) +function KnitServer.BindRemoteEvent(service: Service, eventName: string, remoteEvent) assert(service._knit_re[eventName] == nil, "RemoteEvent \"" .. eventName .. "\" already exists") local re = remoteEvent._remote re.Name = eventName @@ -122,19 +153,19 @@ function KnitServer.BindRemoteEvent(service, eventName, remoteEvent) end -function KnitServer.BindRemoteFunction(service, funcName, func) +function KnitServer.BindRemoteFunction(service: Service, funcName: string, func: (ServiceClient, ...any) -> ...any) assert(service._knit_rf[funcName] == nil, "RemoteFunction \"" .. funcName .. "\" already exists") local rf = Instance.new("RemoteFunction") rf.Name = funcName service._knit_rf[funcName] = rf AddToRepFolder(service, rf) - function rf.OnServerInvoke(...) + rf.OnServerInvoke = function(...) return Ser.SerializeArgsAndUnpack(func(service.Client, Ser.DeserializeArgsAndUnpack(...))) end end -function KnitServer.BindRemoteProperty(service, propName, prop) +function KnitServer.BindRemoteProperty(service: Service, propName: string, prop) assert(service._knit_rp[propName] == nil, "RemoteProperty \"" .. propName .. "\" already exists") prop._object.Name = propName service._knit_rp[propName] = prop @@ -144,7 +175,7 @@ end function KnitServer.Start() - if (started) then + if started then return Promise.Reject("Knit already started") end @@ -157,13 +188,13 @@ function KnitServer.Start() -- Bind remotes: for _,service in pairs(services) do for k,v in pairs(service.Client) do - if (type(v) == "function") then + if type(v) == "function" then KnitServer.BindRemoteFunction(service, k, v) - elseif (RemoteSignal.Is(v)) then + elseif RemoteSignal.Is(v) then KnitServer.BindRemoteEvent(service, k, v) - elseif (RemoteProperty.Is(v)) then + elseif RemoteProperty.Is(v) then KnitServer.BindRemoteProperty(service, k, v) - elseif (Signal.Is(v)) then + elseif Signal.Is(v) then warn("Found Signal instead of RemoteSignal (Knit.Util.RemoteSignal). Please change to RemoteSignal. [" .. service.Name .. ".Client." .. k .. "]") end end @@ -172,7 +203,7 @@ function KnitServer.Start() -- Init: local promisesInitServices = {} for _,service in pairs(services) do - if (type(service.KnitInit) == "function") then + if type(service.KnitInit) == "function" then table.insert(promisesInitServices, Promise.new(function(r) service:KnitInit() r() @@ -186,7 +217,7 @@ function KnitServer.Start() -- Start: for _,service in pairs(services) do - if (type(service.KnitStart) == "function") then + if type(service.KnitStart) == "function" then task.spawn(service.KnitStart, service) end end @@ -207,7 +238,7 @@ end function KnitServer.OnStart() - if (startedComplete) then + if startedComplete then return Promise.Resolve() else return Promise.FromEvent(onStartedComplete.Event) diff --git a/src/Util/Component.lua b/src/Util/Component.lua index 1e7ea92e..1cd13366 100644 --- a/src/Util/Component.lua +++ b/src/Util/Component.lua @@ -105,7 +105,7 @@ local componentByTagDestroyed = Signal.new() local function IsDescendantOfWhitelist(instance) for _,v in ipairs(DESCENDANT_WHITELIST) do - if (instance:IsDescendantOf(v)) then + if instance:IsDescendantOf(v) then return true end end @@ -123,18 +123,18 @@ function Component.ObserveFromTag(tag, observer) local observeJanitor = Janitor.new() janitor:Add(observeJanitor) local function OnCreated(component) - if (component._tag == tag) then + if component._tag == tag then observer(component, observeJanitor) end end local function OnDestroyed(component) - if (component._tag == tag) then + if component._tag == tag then observeJanitor:Cleanup() end end do local component = Component.FromTag(tag) - if (component) then + if component then task.spawn(OnCreated, component) end end @@ -152,12 +152,12 @@ function Component.Auto(folder) Component.new(m.Tag, m, m.RenderPriority, m.RequiredComponents) end for _,v in ipairs(folder:GetDescendants()) do - if (v:IsA("ModuleScript")) then + if v:IsA("ModuleScript") then Setup(v) end end folder.DescendantAdded:Connect(function(v) - if (v:IsA("ModuleScript")) then + if v:IsA("ModuleScript") then Setup(v) end end) @@ -201,7 +201,7 @@ function Component.new(tag, class, renderPriority, requireComponents) local function HasRequiredComponents(instance) for _,reqComp in ipairs(self._requireComponents) do local comp = Component.FromTag(reqComp) - if (comp:GetFromInstance(instance) == nil) then + if comp:GetFromInstance(instance) == nil then return false end end @@ -209,7 +209,7 @@ function Component.new(tag, class, renderPriority, requireComponents) end observeJanitor:Add(CollectionService:GetInstanceAddedSignal(tag):Connect(function(instance) - if (IsDescendantOfWhitelist(instance) and HasRequiredComponents(instance)) then + if IsDescendantOfWhitelist(instance) and HasRequiredComponents(instance) then self:_instanceAdded(instance) end end)) @@ -221,12 +221,12 @@ function Component.new(tag, class, renderPriority, requireComponents) for _,reqComp in ipairs(self._requireComponents) do local comp = Component.FromTag(reqComp) observeJanitor:Add(comp.Added:Connect(function(obj) - if (CollectionService:HasTag(obj.Instance, tag) and HasRequiredComponents(obj.Instance)) then + if CollectionService:HasTag(obj.Instance, tag) and HasRequiredComponents(obj.Instance) then self:_instanceAdded(obj.Instance) end end)) observeJanitor:Add(comp.Removed:Connect(function(obj) - if (CollectionService:HasTag(obj.Instance, tag)) then + if CollectionService:HasTag(obj.Instance, tag) then self:_instanceRemoved(obj.Instance) end end)) @@ -242,7 +242,7 @@ function Component.new(tag, class, renderPriority, requireComponents) do local b = Instance.new("BindableEvent") for _,instance in ipairs(CollectionService:GetTagged(tag)) do - if (IsDescendantOfWhitelist(instance) and HasRequiredComponents(instance)) then + if IsDescendantOfWhitelist(instance) and HasRequiredComponents(instance) then local c = b.Event:Connect(function() self:_instanceAdded(instance) end) @@ -255,14 +255,14 @@ function Component.new(tag, class, renderPriority, requireComponents) end - if (#self._requireComponents == 0) then + if #self._requireComponents == 0 then ObserveTag() else -- Only observe tag when all required components are available: local tagsReady = {} local function Check() for _,ready in pairs(tagsReady) do - if (not ready) then + if not ready then return end end @@ -334,13 +334,13 @@ end function Component:_startLifecycle() self._lifecycle = true - if (self._hasHeartbeatUpdate) then + if self._hasHeartbeatUpdate then self:_startHeartbeatUpdate() end - if (self._hasSteppedUpdate) then + if self._hasSteppedUpdate then self:_startSteppedUpdate() end - if (self._hasRenderUpdate) then + if self._hasRenderUpdate then self:_startRenderUpdate() end end @@ -353,13 +353,13 @@ end function Component:_instanceAdded(instance) - if (self._instancesToObjects[instance]) then return end - if (not self._lifecycle) then + if self._instancesToObjects[instance] then return end + if not self._lifecycle then self:_startLifecycle() end self._nextId = (self._nextId + 1) local id = (self._tag .. tostring(self._nextId)) - if (IS_SERVER) then + if IS_SERVER then instance:SetAttribute(ATTRIBUTE_ID_NAME, id) end local obj = self._class.new(instance) @@ -367,9 +367,9 @@ function Component:_instanceAdded(instance) obj._id = id self._instancesToObjects[instance] = obj table.insert(self._objects, obj) - if (self._hasInit) then + if self._hasInit then task.defer(function() - if (self._instancesToObjects[instance] ~= obj) then return end + if self._instancesToObjects[instance] ~= obj then return end obj:Init() end) end @@ -379,14 +379,14 @@ end function Component:_instanceRemoved(instance) - if (not self._instancesToObjects[instance]) then return end + if not self._instancesToObjects[instance] then return end self._instancesToObjects[instance] = nil for i,obj in ipairs(self._objects) do - if (obj.Instance == instance) then - if (self._hasDeinit) then + if obj.Instance == instance then + if self._hasDeinit then obj:Deinit() end - if (IS_SERVER and instance.Parent and instance:GetAttribute(ATTRIBUTE_ID_NAME) ~= nil) then + if IS_SERVER and instance.Parent and instance:GetAttribute(ATTRIBUTE_ID_NAME) ~= nil then instance:SetAttribute(ATTRIBUTE_ID_NAME, nil) end self.Removed:Fire(obj) @@ -396,7 +396,7 @@ function Component:_instanceRemoved(instance) break end end - if (#self._objects == 0 and self._lifecycle) then + if #self._objects == 0 and self._lifecycle then self:_stopLifecycle() end end @@ -414,7 +414,7 @@ end function Component:GetFromID(id) for _,v in ipairs(self._objects) do - if (v._id == id) then + if v._id == id then return v end end @@ -433,7 +433,7 @@ function Component:WaitFor(instance, timeout) return ((isName and obj.Instance.Name == instance) or ((not isName) and obj.Instance == instance)) end for _,obj in ipairs(self._objects) do - if (IsInstanceValid(obj)) then + if IsInstanceValid(obj) then return Promise.Resolve(obj) end end @@ -452,17 +452,17 @@ function Component:Observe(instance, observer) local observeJanitor = Janitor.new() janitor:Add(observeJanitor) janitor:Add(self.Added:Connect(function(obj) - if (obj.Instance == instance) then + if obj.Instance == instance then observer(obj, observeJanitor) end end)) janitor:Add(self.Removed:Connect(function(obj) - if (obj.Instance == instance) then + if obj.Instance == instance then observeJanitor:Cleanup() end end)) for _,obj in ipairs(self._objects) do - if (obj.Instance == instance) then + if obj.Instance == instance then task.spawn(observer, obj, observeJanitor) break end diff --git a/src/Util/EnumList.lua b/src/Util/EnumList.lua index 677f51ed..6da2f081 100644 --- a/src/Util/EnumList.lua +++ b/src/Util/EnumList.lua @@ -37,9 +37,9 @@ function EnumList.new(name: string, enums: EnumNames) _scope = scope; }, { __index = function(_t, k) - if (enumItems[k]) then + if enumItems[k] then return enumItems[k] - elseif (EnumList[k]) then + elseif EnumList[k] then return EnumList[k] else error("Unknown " .. name .. ": " .. tostring(k), 2) diff --git a/src/Util/Janitor.lua b/src/Util/Janitor.lua index 05338e13..1586d3b8 100644 --- a/src/Util/Janitor.lua +++ b/src/Util/Janitor.lua @@ -4,9 +4,7 @@ -- roblox-ts support by OverHash and Validark -- LinkToInstance fixed by Elttob. -local RunService = game:GetService("RunService") local Promise = require(script.Parent.Promise) -local Heartbeat = RunService.Heartbeat local IndicesReference = newproxy(true) getmetatable(IndicesReference).__tostring = function() @@ -59,7 +57,7 @@ end @param [t:any] Object The object you want to clean up. @param [t:string|true?] MethodName The name of the method that will be used to clean up. If not passed, it will first check if the object's type exists in TypeDefaults, and if that doesn't exist, it assumes `Destroy`. @param [t:any?] Index The index that can be used to clean up the object manually. - @returns [t:any] The object that was passed. + @returns [t:any] The object that was passed as the first argument. **--]] function Janitor.__index:Add(Object, MethodName, Index) if Index then @@ -76,7 +74,7 @@ function Janitor.__index:Add(Object, MethodName, Index) MethodName = MethodName or TypeDefaults[typeof(Object)] or "Destroy" if type(Object) ~= "function" and not Object[MethodName] then - warn(string.format(METHOD_NOT_FOUND_ERROR, tostring(Object), tostring(MethodName), debug.traceback(nil, 2))) + warn(string.format(METHOD_NOT_FOUND_ERROR, tostring(Object), tostring(MethodName), debug.traceback(nil :: any, 2))) end self[Object] = MethodName @@ -148,6 +146,8 @@ function Janitor.__index:Get(Index) local This = self[IndicesReference] if This then return This[Index] + else + return nil end end @@ -221,7 +221,7 @@ end "Links" this Janitor to an Instance, such that the Janitor will `Cleanup` when the Instance is `Destroyed()` and garbage collected. A Janitor may only be linked to one instance at a time, unless `AllowMultiple` is true. When called with a truthy `AllowMultiple` parameter, the Janitor will "link" the Instance without overwriting any previous links, and will also not be overwritable. When called with a falsy `AllowMultiple` parameter, the Janitor will overwrite the previous link which was also called with a falsy `AllowMultiple` parameter, if applicable. @param [t:Instance] Object The instance you want to link the Janitor to. @param [t:boolean?] AllowMultiple Whether or not to allow multiple links on the same Janitor. - @returns [t:RbxScriptConnection] A pseudo RBXScriptConnection that can be disconnected. + @returns [t:RbxScriptConnection] A pseudo RBXScriptConnection that can be disconnected to prevent the cleanup of LinkToInstance. **--]] function Janitor.__index:LinkToInstance(Object, AllowMultiple) local Connection @@ -235,22 +235,21 @@ function Janitor.__index:LinkToInstance(Object, AllowMultiple) IsNilParented = NewParent == nil if IsNilParented then - coroutine.wrap(function() - Heartbeat:Wait() + task.defer(function() if not ManualDisconnect.Connected then return elseif not Connection.Connected then self:Cleanup() else while IsNilParented and Connection.Connected and ManualDisconnect.Connected do - Heartbeat:Wait() + task.wait() end if ManualDisconnect.Connected and IsNilParented then self:Cleanup() end end - end)() + end) end end end @@ -269,7 +268,7 @@ end --[[** Links several instances to a janitor, which is then returned. @param [t:...Instance] ... All the instances you want linked. - @returns [t:Janitor] A janitor that can be used to manually disconnect all LinkToInstances. + @returns [t:Janitor] A new janitor that can be used to manually disconnect all LinkToInstances. **--]] function Janitor.__index:LinkToInstances(...) local ManualCleanup = Janitor.new() diff --git a/src/Util/Loader.lua b/src/Util/Loader.lua index 8ad212c9..17c32fc7 100644 --- a/src/Util/Loader.lua +++ b/src/Util/Loader.lua @@ -23,7 +23,7 @@ type Modules = {Module} function Loader.LoadChildren(parent: Instance): Modules local modules: Modules = {} for _,child in ipairs(parent:GetChildren()) do - if (child:IsA("ModuleScript")) then + if child:IsA("ModuleScript") then local m = require(child) table.insert(modules, m) end @@ -35,7 +35,7 @@ end function Loader.LoadDescendants(parent: Instance): Modules local modules: Modules = {} for _,descendant in ipairs(parent:GetDescendants()) do - if (descendant:IsA("ModuleScript")) then + if descendant:IsA("ModuleScript") then local m = require(descendant) table.insert(modules, m) end diff --git a/src/Util/Option.lua b/src/Util/Option.lua index c37da217..1fd98337 100644 --- a/src/Util/Option.lua +++ b/src/Util/Option.lua @@ -104,7 +104,7 @@ end function Option.Wrap(value) - if (value == nil) then + if value == nil then return Option.None else return Option.Some(value) @@ -113,7 +113,7 @@ end function Option.Is(obj) - return (type(obj) == "table" and getmetatable(obj) == Option) + return type(obj) == "table" and getmetatable(obj) == Option end @@ -124,7 +124,7 @@ end function Option.Deserialize(data) -- type data = {ClassName: string, Value: any} assert(type(data) == "table" and data.ClassName == CLASSNAME, "Invalid data for deserializing Option") - return (data.Value == nil and Option.None or Option.Some(data.Value)) + return data.Value == nil and Option.None or Option.Some(data.Value) end @@ -141,7 +141,7 @@ function Option:Match(matches) local onNone = matches.None assert(type(onSome) == "function", "Missing 'Some' match") assert(type(onNone) == "function", "Missing 'None' match") - if (self:IsSome()) then + if self:IsSome() then return onSome(self:Unwrap()) else return onNone() @@ -176,7 +176,7 @@ end function Option:UnwrapOr(default) - if (self:IsSome()) then + if self:IsSome() then return self:Unwrap() else return default @@ -185,7 +185,7 @@ end function Option:UnwrapOrElse(defaultFunc) - if (self:IsSome()) then + if self:IsSome() then return self:Unwrap() else return defaultFunc() @@ -194,7 +194,7 @@ end function Option:And(optB) - if (self:IsSome()) then + if self:IsSome() then return optB else return Option.None @@ -203,7 +203,7 @@ end function Option:AndThen(andThenFunc) - if (self:IsSome()) then + if self:IsSome() then local result = andThenFunc(self:Unwrap()) Option.Assert(result) return result @@ -214,7 +214,7 @@ end function Option:Or(optB) - if (self:IsSome()) then + if self:IsSome() then return self else return optB @@ -223,7 +223,7 @@ end function Option:OrElse(orElseFunc) - if (self:IsSome()) then + if self:IsSome() then return self else local result = orElseFunc() @@ -236,9 +236,9 @@ end function Option:XOr(optB) local someOptA = self:IsSome() local someOptB = optB:IsSome() - if (someOptA == someOptB) then + if someOptA == someOptB then return Option.None - elseif (someOptA) then + elseif someOptA then return self else return optB @@ -247,7 +247,7 @@ end function Option:Filter(predicate) - if (self:IsNone() or not predicate(self._v)) then + if self:IsNone() or not predicate(self._v) then return Option.None else return self @@ -256,12 +256,12 @@ end function Option:Contains(value) - return (self:IsSome() and self._v == value) + return self:IsSome() and self._v == value end function Option:__tostring() - if (self:IsSome()) then + if self:IsSome() then return ("Option<" .. typeof(self._v) .. ">") else return "Option" @@ -270,10 +270,10 @@ end function Option:__eq(opt) - if (Option.Is(opt)) then - if (self:IsSome() and opt:IsSome()) then + if Option.Is(opt) then + if self:IsSome() and opt:IsSome() then return (self:Unwrap() == opt:Unwrap()) - elseif (self:IsNone() and opt:IsNone()) then + elseif self:IsNone() and opt:IsNone() then return true end end diff --git a/src/Util/Remote/ClientRemoteProperty.lua b/src/Util/Remote/ClientRemoteProperty.lua index 829e4950..aa705a62 100644 --- a/src/Util/Remote/ClientRemoteProperty.lua +++ b/src/Util/Remote/ClientRemoteProperty.lua @@ -35,7 +35,7 @@ function ClientRemoteProperty.new(object) self._value = v end - if (self._isTable) then + if self._isTable then self.Changed = Signal.new() self._change = object.OnClientEvent:Connect(function(tbl) SetValue(tbl) @@ -60,7 +60,7 @@ end function ClientRemoteProperty:Destroy() self._change:Disconnect() - if (self._isTable) then + if self._isTable then self.Changed:Destroy() end end diff --git a/src/Util/Remote/ClientRemoteSignal.lua b/src/Util/Remote/ClientRemoteSignal.lua index d9b3139b..a8e4da0a 100644 --- a/src/Util/Remote/ClientRemoteSignal.lua +++ b/src/Util/Remote/ClientRemoteSignal.lua @@ -34,22 +34,22 @@ function Connection.new(event, connection) end function Connection:IsConnected() - if (self._conn) then + if self._conn then return self._conn.Connected end return false end function Connection:Disconnect() - if (self._conn) then + if self._conn then self._conn:Disconnect() self._conn = nil end - if (not self._event) then return end + if not self._event then return end self.Connected = false local connections = self._event._connections for i,c in ipairs(connections) do - if (c == self) then + if c == self then connections[i] = connections[#connections] connections[#connections] = nil break @@ -69,7 +69,7 @@ ClientRemoteSignal.__index = ClientRemoteSignal function ClientRemoteSignal.Is(object) - return (type(object) == "table" and getmetatable(object) == ClientRemoteSignal) + return type(object) == "table" and getmetatable(object) == ClientRemoteSignal end @@ -106,7 +106,7 @@ end function ClientRemoteSignal:Destroy() for _,c in ipairs(self._connections) do - if (c._conn) then + if c._conn then c._conn:Disconnect() end end diff --git a/src/Util/Remote/RemoteProperty.lua b/src/Util/Remote/RemoteProperty.lua index e63e477f..d80f306a 100644 --- a/src/Util/Remote/RemoteProperty.lua +++ b/src/Util/Remote/RemoteProperty.lua @@ -40,7 +40,7 @@ RemoteProperty.__index = RemoteProperty function RemoteProperty.Is(object) - return (type(object) == "table" and getmetatable(object) == RemoteProperty) + return type(object) == "table" and getmetatable(object) == RemoteProperty end @@ -48,7 +48,7 @@ function RemoteProperty.new(value, overrideClass) assert(IS_SERVER, "RemoteProperty can only be created on the server") - if (overrideClass ~= nil) then + if overrideClass ~= nil then assert(type(overrideClass) == "string", "OverrideClass must be a string; got " .. type(overrideClass)) assert(overrideClass:match("Value$"), "OverrideClass must be of super type ValueBase (e.g. IntValue); got " .. overrideClass) end @@ -64,7 +64,7 @@ function RemoteProperty.new(value, overrideClass) _object = Instance.new(class); }, RemoteProperty) - if (self._isTable) then + if self._isTable then local req = Instance.new("RemoteFunction") req.Name = "TableRequest" req.Parent = self._object @@ -84,14 +84,14 @@ end function RemoteProperty:Replicate() - if (self._isTable) then + if self._isTable then self:Set(self._value) end end function RemoteProperty:Set(value) - if (self._isTable) then + if self._isTable then self._object:FireAllClients(value) self.Changed:Fire(value) else diff --git a/src/Util/Remote/RemoteSignal.lua b/src/Util/Remote/RemoteSignal.lua index 79ad2837..80bf7d7e 100644 --- a/src/Util/Remote/RemoteSignal.lua +++ b/src/Util/Remote/RemoteSignal.lua @@ -26,7 +26,7 @@ RemoteSignal.__index = RemoteSignal function RemoteSignal.Is(object) - return (type(object) == "table" and getmetatable(object) == RemoteSignal) + return type(object) == "table" and getmetatable(object) == RemoteSignal end @@ -52,7 +52,7 @@ end function RemoteSignal:FireExcept(player, ...) local args = Ser.SerializeArgs(...) for _,plr in ipairs(Players:GetPlayers()) do - if (plr ~= player) then + if plr ~= player then self._remote:FireClient(plr, Ser.UnpackArgs(args)) end end diff --git a/src/Util/Ser.lua b/src/Util/Ser.lua index 75c05086..7bd5b58b 100644 --- a/src/Util/Ser.lua +++ b/src/Util/Ser.lua @@ -70,9 +70,9 @@ end function Ser.DeserializeArgs(...: any): Args local args = table.pack(...) for i,arg in ipairs(args) do - if (type(arg) == "table") then + if type(arg) == "table" then local ser = Ser.Classes[arg.ClassName] - if (ser) then + if ser then args[i] = ser.Deserialize(arg) end end @@ -88,9 +88,9 @@ end function Ser.Serialize(value: any): any - if (type(value) == "table") then + if type(value) == "table" then local ser = Ser.Classes[value.ClassName] - if (ser) then + if ser then value = ser.Serialize(value) end end @@ -99,9 +99,9 @@ end function Ser.Deserialize(value: any): any - if (type(value) == "table") then + if type(value) == "table" then local ser = Ser.Classes[value.ClassName] - if (ser) then + if ser then value = ser.Deserialize(value) end end diff --git a/src/Util/Timer.lua b/src/Util/Timer.lua index 666c23e7..5082f42c 100644 --- a/src/Util/Timer.lua +++ b/src/Util/Timer.lua @@ -24,8 +24,7 @@ --]] -local Knit = require(game:GetService("ReplicatedStorage").Knit) -local Signal = require(Knit.Util.Signal) +local Signal = require(script.Parent.Signal) local RunService = game:GetService("RunService") @@ -55,7 +54,7 @@ end function Timer:Start() if self._runHandle then return end - local n = 0 + local n = 1 local start = time() local nextTick = start + self.Interval self._runHandle = RunService.Heartbeat:Connect(function() diff --git a/src/Version.txt b/src/Version.txt index 4dba2fb9..e45fd909 100644 --- a/src/Version.txt +++ b/src/Version.txt @@ -1 +1 @@ -0.0.19-alpha \ No newline at end of file +0.0.20-alpha \ No newline at end of file diff --git a/src/init.lua b/src/init.lua index 94aee285..da5ba5b7 100644 --- a/src/init.lua +++ b/src/init.lua @@ -1,6 +1,6 @@ -if (game:GetService("RunService"):IsServer()) then +if game:GetService("RunService"):IsServer() then return require(script.KnitServer) else script.KnitServer:Destroy() return require(script.KnitClient) -end \ No newline at end of file +end diff --git a/test.project.json b/test.project.json index e1cd9849..7562d4cd 100644 --- a/test.project.json +++ b/test.project.json @@ -39,4 +39,4 @@ } } } -} \ No newline at end of file +} diff --git a/test/tests/integration/server/Services/MyService.lua b/test/tests/integration/server/Services/MyService.lua index f9a2564a..ea223da5 100644 --- a/test/tests/integration/server/Services/MyService.lua +++ b/test/tests/integration/server/Services/MyService.lua @@ -1,5 +1,6 @@ local Knit = require(game:GetService("ReplicatedStorage").Knit) local Option = require(Knit.Util.Option) +local Timer = require(Knit.Util.Timer) local MyService = Knit.CreateService { @@ -16,7 +17,7 @@ end function MyService.Client:MaybeGetRandomNumber(_player) local rng = Random.new() local num = rng:NextNumber() - if (num < 0.5) then + if num < 0.5 then return Option.Some(num) else return Option.None @@ -26,6 +27,14 @@ end function MyService:KnitStart() print(self.Name .. " started") + local timer = Timer.new(1) + timer.Tick:Connect(function() + print("TICK", time()) + end) + timer:Start() + task.delay(5, function() + timer:Destroy() + end) end diff --git a/test/tests/unit/util/Component.spec.lua b/test/tests/unit/util/Component.spec.lua index d8b23774..c77247c2 100644 --- a/test/tests/unit/util/Component.spec.lua +++ b/test/tests/unit/util/Component.spec.lua @@ -136,7 +136,7 @@ return function() local runtimeSuccess = Promise.new(function(resolve, _reject, onCancel) local handle handle = game:GetService("RunService").Heartbeat:Connect(function() - if (obj.DidHeartbeatUpdate and obj.DidSteppedUpdate and obj.DidRenderUpdate) then + if obj.DidHeartbeatUpdate and obj.DidSteppedUpdate and obj.DidRenderUpdate then resolve() end end) @@ -153,9 +153,9 @@ return function() local initDeinitSuccess = Promise.new(function(resolve, _reject, onCancel) local handle handle = game:GetService("RunService").Heartbeat:Connect(function() - if (obj.DidDeinit) then + if obj.DidDeinit then resolve() - elseif (obj.DidInit and instance.Parent) then + elseif obj.DidInit and instance.Parent then instance:Destroy() end end) diff --git a/test/tests/unit/util/Signal.spec.lua b/test/tests/unit/util/Signal.spec.lua index 34506c03..85c0f578 100644 --- a/test/tests/unit/util/Signal.spec.lua +++ b/test/tests/unit/util/Signal.spec.lua @@ -1,9 +1,9 @@ local function AwaitCondition(predicate, timeout) local start = os.clock() timeout = (timeout or 10) - while (true) do - if (predicate()) then return true end - if ((os.clock() - start) > timeout) then return false end + while true do + if predicate() then return true end + if (os.clock() - start) > timeout then return false end task.wait() end end diff --git a/test/tests/unit/util/Timer.spec.lua b/test/tests/unit/util/Timer.spec.lua index def2d5fa..40de68b0 100644 --- a/test/tests/unit/util/Timer.spec.lua +++ b/test/tests/unit/util/Timer.spec.lua @@ -12,7 +12,7 @@ return function() end) afterEach(function() - if (timer) then + if timer then timer:Destroy() timer = nil end @@ -34,7 +34,7 @@ return function() local start = time() local stop = nil timer.Tick:Connect(function() - if (not stop) then + if not stop then stop = time() end end) diff --git a/testez.toml b/testez.toml index 3cfc0408..c2c5a984 100644 --- a/testez.toml +++ b/testez.toml @@ -63,4 +63,4 @@ type = "string" type = "function" [SKIP] -args = [] \ No newline at end of file +args = []