Instance | A lightweight, extended Instance module

Version 2.00 (1 March 2024)


Introduction

Hello, I just released a module that extends the functionality of Roblox’s Instance.new constructor by replacing the parent parameter with a properties: {[string]: any} optional parameter, including an additional children: {Instance} optional parameter and attributes: {[any]: any} optional parameter.

This module also has built in functions that you can use to speed up development (usage and examples below).

List of familiar functions:

  • Instance:FindFirstAncestorWithFilter()
  • Instance:FindFirstChildWithFilter()
  • Instance:FindFirstDescendantWithFilter()
  • Instance:GetChildrenWithFilter()
  • Instance:GetDescendantsWithFilter()
  • Instance:WaitForChildWithFilter()

List of custom functions:

  • Instance:WaitForDescendantWithFilter()
  • Instance:FolderToDataTable()
  • Instance:DataTableToFolder()
  • Instance:DataTableToFolderUsingValueBases()
  • Instance:DataTableToFolderUsingAttributes()

Setup and Usage

Grab the model below and insert it in your game (I recommend ReplicatedStorage). The model contains the Instance ModuleScript that you can then override the global Instance variable.

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Instance = require(ReplicatedStorage.Instance) -- WaitForChild if you are using a LocalScript

Model Link and Examples

You can grab the latest module anytime here:

Here is an example of the full functionality of this module:

-- Require
local Instance = require("PATH_TO_MODULE")

-- Instance.new(className: string, properties: {[string]: any}?, children: {Instance}?, attributes: {[string]: any}?): Instance
-- Note: It is OK to pass a Parent key. Internally, parenting will be done after all other properties and children has been set
-- Additionally, any childrens passed in this function will automatically parent to the created Instance
-- Read: https://create.roblox.com/docs/reference/engine/classes/Instance#Parent -> Object Replication
local part: Part = Instance.new("Part", {
	Name = "MyPart",
	Anchored = true,
	Transparency = 0.2,
	BrickColor = BrickColor.random(),
	Parent = workspace
}, {
	Instance.new("StringValue"),
	Instance.new("BoolValue"),
	Instance.new("IntValue", {
		Name = "MyIntValue",
		Value = 200
	})
}, {
	SomeAttribute = "Value",
	AnotherAttribute = true
})

-- Note: All filter functions passed will check for truthy/falsy values

-- Instance:FindFirstAncestorWithFilter(instance: Instance, filterFunction: (ancestor: Instance) -> any): Instance?
local someDeepAccessoryPart = nil -- path to some deep accessory part
local playerCharacterModel: Model = Instance:FindFirstAncestorWithFilter(someDeepAccessoryPart, function(ancestor)
	return ancestor.ClassName == "Model" and game:GetService("Players"):GetPlayerFromCharacter(ancestor) ~= nil
end)

-- Instance:FindFirstChildWithFilter(instance: Instance, filterFunction: (child: Instance) -> any): Instance?
local playerToolWithHandle: Tool = Instance:FindFirstChildWithFilter(playerCharacterModel, function(child)
	return child.ClassName == "Tool" and child:FindFirstChild("Handle") ~= nil
end)

-- Instance:FindFirstDescendantWithFilter(instance: Instance, filterFunction: (descendant: Instance) -> any): Instance?
local toolMainTrail: Trail = Instance:FindFirstDescendantWithFilter(playerToolWithHandle, function(descendant)
	return descendant.ClassName == "Trail" and descendant.Name == "MainTrail"
end)

-- Instance:GetChildrenWithFilter(instance: Instance, filterFunction: (child: Instance) -> any): {Instance?}
local playerCharactersThatAreAlive: {Model} = Instance:GetChildrenWithFilter(workspace, function(child)
	local player = game:GetService("Players"):GetPlayerFromCharacter(child)
	local humanoid = if player ~= nil then Instance:FindFirstChildWithFilter(child, function(childFromCharacter: Humanoid) -- For autofill purposes
		return childFromCharacter.ClassName == "Humanoid" and childFromCharacter:GetState() ~= Enum.HumanoidStateType.Dead
	end) else nil

	return humanoid ~= nil
end)

-- Instance:GetDescendantsWithFilter(instance: Instance, filterFunction: (descendant: Instance) -> any): {Instance?}
local function cleanupSounds()
	local soundsWithNoSoundId: {Sound} = Instance:GetDescendantsWithFilter(workspace, function(descendant: Sound) -- For autofill purposes
		return descendant.ClassName == "Sound" and descendant.SoundId == ""
	end)

	for _, sound in ipairs(soundsWithNoSoundId) do
		sound:Destroy()
	end
end

-- Instance:WaitForChildWithFilter(instance: Instance, filterFunction: (child: Instance) -> any, timeOut: number?): Instance?
-- Note: Replicates the original WaitForChild by using threads and connections with replicated timeOut behavior

-- Will yield indefinitely until conditions have been met and something gets returned
local attackRemoteEvent: RemoteEvent = Instance:WaitForChildWithFilter(game:GetService("ReplicatedStorage"), function(child)
	return child.ClassName == "RemoteEvent" and child.Name == "Attack"
end)

-- Will yield until conditions have been met and something gets returned or 5 seconds have passed and will return nil
local attackSound: Sound = Instance:WaitForChildWithFilter(game:GetService("ReplicatedStorage"), function(child)
	return child.ClassName == "Sound" and child.Name == "Attack"
end, 5)

-- Instance:WaitForDescendantWithFilter(instance: Instance, filterFunction: (descendant: Instance) -> any, timeOut: number?): Instance?
-- Note: Same functionality as Instance:WaitForChildWithFilter()
local superSecretPart: Part = Instance:WaitForDescendantWithFilter(workspace, function(descendant)
	return descendant:IsA("BasePart") == true and descendant.Name == "SuperSecretPart"
end)

-- Instance:FolderToDataTable(instance: Folder | Instance): DataTable ({[any]: any})
-- Converts a folder (or instance) and it's descendants into a data table (DataTable)
-- Note: Compatible with both ValueBases and Attributes

-- Example mock data folder
local function newMockDataFolder(): Folder
	return Instance.new("Folder", {
		Name = "PlayerData",
		Parent = script.Parent
	}, {
		Instance.new("IntValue", {
			Name = "Level",
			Value = 1
		}),
		Instance.new("IntValue", {
			Name = "Experience",
			Value = 0
		}),
		Instance.new("Folder", {
			Name = "InventoryArrayUsingBaseValues"
		}, {
			Instance.new("StringValue", {
				Name = "1",
				Value = "Apple",
			}),
			Instance.new("StringValue", {
				Name = "2",
				Value = "Banana",
			})
		}),
		Instance.new("Folder", {
			Name = "InventoryArrayUsingAttributes"
		}, {
			Instance.new("Folder", {
				Name = "Apple",
			}, nil, {
				Count = 5,
				Description = "Yummy!"
			}),
			Instance.new("Folder", {
				Name = "Banana",
			}, nil, {
				Count = 1,
				Description = "Even more yummy!"
			})
		})
	}, {
		Locked = true,
		DataStoreLoaded = false
	})
end

local dataFolder: Folder = newMockDataFolder()
local dataTable: {[any]: any} = Instance:FolderToDataTable(dataFolder)
--[=[
	local dataTable = {
		["DataStoreLoaded"] = false,
		["Experience"] = 0,
		["InventoryArrayUsingAttributes"] = {
			["Apple"] = {
				["Count"] = 5,
				["Description"] = "Yummy!"
			},
			["Banana"] = {
				["Count"] = 1,
				["Description"] = "Even more yummy!"
			}
		},
		["InventoryArrayUsingBaseValues"] = {
			[1] = "Apple",
			[2] = "Banana"
		},
		["Level"] = 1,
		["Locked"] = true
	}
]=]

-- instance:DataTableToFolder(tableToConvert: DataTable, numericalKeyShouldCreateValueBase: boolean?, iteratorFunction: IteratorFunction?): Folder
-- Instance:DataTableToFolderUsingValueBases(tableToConvert: DataTable, iteratorFunction: IteratorFunction?): Folder
-- Instance:DataTableToFolderUsingAttributes(tableToConvert: DataTable, numericalKeyShouldCreateValueBase: boolean?, iteratorFunction: IteratorFunction?): Folder

-- Note 1: By default, iteratorFunction will use pairs. If you want to use ipairs,
-- simply pass the "ipairs" function as the iteratorFunction

-- Note 2: By default, numericalKeyShouldCreateValueBase = true, meaning if a key
-- is numerical, it will create a ValueBase instead of setting an Attribute (value)

-- Convert using attributes, then ValueBases if the attribute type is not supported
-- By default, any numerical keys will convert to ValueBases instead unless
-- numericalKeyShouldCreateValueBase is set to false
local dataFolder: Folder = Instance:DataTableToFolder({
	ThisWillBecomeAFolder = {
		ThisWillBecomeAnAttribute = true,
		AlsoAnAttribute = "hooray!",
		ButThisOneWillTurnIntoARayValue = Ray.new(),
		ThisWillAlsoTurnIntoAFolder = {
			"However",
			"This is an array",
			"Meaning by default",
			"This will turn into",
			"StringValues"
		}
	},
	AndFinallyAnAttributeInTheDataFolder = 100
})
dataFolder.Name = "DataTableToFolder"
dataFolder.Parent = script.Parent

-- Convert using ValueBases
local dataFolder: Folder = Instance:DataTableToFolderUsingValueBases({
	ThisWillBecomeAnIntValue = 1,
	ThisWillbecomeANumberValue = 1.001,
	ThisWillBecomeAFolder = {
		etc = "etc"
	}
})
dataFolder.Name = "DataTableToFolderUsingValueBases"
dataFolder.Parent = script.Parent

-- Convert using Attributes
-- Note: This will turn into a singular folder with attributes 1-4 instead
-- If numericalKeyShouldCreateValueBase is set to true, it will create
-- ValueBases (name as the key) instead of setting these as attributes
local dataFolder: Folder = Instance:DataTableToFolderUsingAttributes({
	"This",
	"Is",
	"An",
	"Array"
}, false, ipairs)
dataFolder.Name = "DataTableToFolderUsingAttributes"
dataFolder.Parent = script.Parent
9 Likes

You mean a constructor like React-lua, don’t you?

still a vague awnser.

:+1:

1 Like

Or maybe fusion, charrrrrrrrrrr 30

1 Like

Pretty much, except I just replace the Instance global just for compatibility.

I added all the functionalities in the example script below.

1 Like

Oh, didn’t notice

true, but fusion is like

local Part=New("Part")({
  [Children]={
    -- children
  },
  --properties
})

and this instance module doesnt have fusion’s automatic ‘Hydration’.

1 Like

But isn’t this kinda look like [UPDATE: 2.0] SmartCreate. An easy way to create Instances

2 Likes

Yeah, this should add more features to .new() like attributes or event support, since that module has those features. Also, there’s no ‘Children’ property, so I guess it’s still more similar to React-lua than SmartCreate.

2 Likes

Attributes can definitely be added, but it’s really only meant to be a replacement to Instance.new for reverse compatibility without forcing people to use a custom function name like Create. I only made this because I wanted to set properties without rewriting the variable name and to include filter functions to what we are already familiar with, ex. FindFirstChild but with WithFilter after it.

As for event supports, I don’t think there is a need since this is only meant to be a small upgrade with extra functions while keeping the familiarity with the already existing functions.

If I may add to this conversation as well, SmartCreate does have some negatives from my short glance at the Module, like:

  • Having to know that the GetPropertyChangedSignal needs to be constructed with a table with the args and callback keys, which is just not obvious and easily forgettable,
  • And most importantly, RBXScriptConnections does not get returned if created with the Create or Edit functions.

I feel as if there is no reason in including these mundane features by straying from what we are already familiar with when the normal way of connecting events is perfectly fine and second nature to most of us.

1 Like

You could just move this line inside the other check outside the for loop

1 Like

This was done for the same reason as to why you should parent after setting properties.

1 Like

Yeah, I know. But doing that will be the exact same. You parent after the for loop is complete so the parent property isn’t set in the for loop, rather set outside it, so there’s no reason of setting properties after the object being parented.

1 Like

This is not an issue at all. The reason why this is outside is if you decide to pass the children argument, only one replication happens to the client as opposed to 1 + x amount of children parented into the new Instance.

2 Likes

Ah, my bad. Forgot about the children setting part :sad:

1 Like