First party create pattern

Right now, there is a pattern known as create that a huge number of games implement. It allows easy creation of a roblox instance, which are often heavy with properties and child objects. Normally to create a roblox instance you have to make it and then set all its properties individually, and then do that for every object in the tree.

local partWithADecal = Instance.new('Part')
partWithADecal.Name = 'PartWithADecal'
partWithADecal.Transparency = 1
partWithADecal.CFrame = CFrame.new(0, 5, 0)
partWithADecal.Anchored = true
partWithADecal.CanCollide = false
partWithADecal.CanQuery = false
partWithADecal.CanTouch = false

local decalInsideThePart = Instance.new('Decal')
decalInsideThePart.Texture = 'something'
decalInsideThePart.Face = Enum.NormalId.Bottom
decalInsideThePart.Parent = partWithADecal

partWithADecal.Parent = workspace

This is obviously annoying. Annoying to write, annoying to read, annoying to modify. The create pattern is so common because it solves all of these problems. It follows the formula create 'name' {props and children}.

partWithADecal = create 'Part' {
   Name = 'PartWithADecal',
   Transparency = 1,
   CFrame = CFrame.new(0, 5, 0),
   Anchored = true,
   CanCollide = false,
   CanQuery = false,
   CanTouch = false,

   create 'Decal' {
      Texture = 'something',
      Face = Enum.NormalId.Back,
   }
}

partWithADecal.Parent = workspace

I want this to be a first-party api that I don’t have to import into every script in my game.

I think the ideal interface is simply calling the existing Instance library as a function.

anchoredPart = Instance 'Part' {
   Anchored = true
}

Elegant and simple.

17 Likes

Since Instance is already defined to be a table, wouldn’t this require adding a metatable to the Instance table?

Which version of the create should be implemented? I’ve looked at the RbxUtility implementation of this and it supports more than what you mentioned, here is an example:

-- create is a table with a __call metamethod, that function will take the type and return another function
-- that will do all of the work (including creating the instance) and return the instance
local part = create"Part"{
	Position = Vector3.new(0,10,0), -- string key = property
	TopSurface = Enum.SurfaceType.Smooth,
	BottomSurface = Enum.SurfaceType.Smooth,
	Anchored = true,
	Parent = workspace, -- the parent property is special, it is set after doing everything else
	create"Decal"{ -- numeric key = set the parent of the value to the new instance
		Texture = "rbxasset://textures/face.png",
		Face = Enum.NormalId.Bottom
	},
	[create.E"Touched"] = function() -- create.E creates a table with __eventname set to the first argument provided
		print"part was touched!" -- using it as a key will connect the value to the event with the name specified by __eventname
	end,
	[create] = function(part) -- defining a key to be the create function itself will call the value just before setting the parent
		-- the created instance is passed to the function
		print(part.Anchored) --> true
		print(part.Parent) --> nil
	end
}

It might be beneficial to have something to add attributes (something like create.A which is similar to create.E) and something to listen to attribute changes (create.AE maybe).

I see two problems with a create function which uses a table like this: the order of iteration over the table is unspecified, and a useless table will be created almost every time.

When multiple entries are specified, in what order will their associated actions be performed? Usually parent is done last, but there may be some properties where the order matters. If there is a way to connect to signals like with RbxUtility.Create then it will definitely matter.

A new table will be probably be created for each instance created using this method, which is more memory allocated and more work for the GC. This could be alleviated by storing the table to avoid creating a new table each time, but that probably won’t happen.

nil also can’t be stored in a table, so there wouldn’t be a way to set a property to nil if it didn’t start as nil (although there probably aren’t any properties like this).

3 Likes

Yes

My post intentionally doesn’t refer to a specific implementation. The final API should be something new, designed with its status as a core library in mind.

Anonymous tables are common and handing multiple sets off to C is way faster than doing it through lua even with the table. Iteration order is not something that needs to be considered. It’s only relevant in special cases, which can either be special cased internally or handled by the developer.

You can use a symbol to represent nil. e.g.

   Parent = Create.Nil
5 Likes