Instance as an Attribute: Introducing the ability to use instances as attributes, providing more flexibility and convenience in storing complex data within attributes.
SetAttributes(instance, attributes): A new method allowing you to set multiple attributes for a given instance in one operation, simplifying attribute assignment and reducing redundancy.
SetInstancesAttribute(instances, key, value): A convenient way to set a common attribute value for multiple instances at once, streamlining the process of assigning attributes to related objects.
GetChildrenWithAttribute(parent, attribute): This new function enables you to retrieve the children of a parent object that have a specific attribute, helping you filter and locate instances more efficiently.
GetDescendantsWithAttribute(ancestor, attribute): A powerful function that allows you to obtain all descendants of a given ancestor object that possess a specified attribute, simplifying complex searches and traversal operations.
ClearAllAttributes(instance): A handy method for quickly removing all attributes associated with a particular instance, facilitating attribute cleanup and management.
FindFirstChildWithAttribute(parent, attribute, recursive): This function enables you to find the first child of a parent object that has a particular attribute, providing a convenient way to search for specific instances within a hierarchy.
WaitForChildWithAttribute(parent, attribute, timeOut): A wait function that waits for a child with the specified attribute to appear under the given parent, allowing you to synchronize actions based on the presence of specific instances.
IsSupportedType(value): A utility function that helps you determine if a given value is a supported attribute type, enabling you to validate attribute values before assigning them.
IsSpecialType(value): Another utility function that assists in identifying whether a value is a special attribute type, helping you handle attribute values with specialized behavior.
Documentation: Creating comprehensive documentation for the AttributeService, providing clear explanations, usage examples, and guidelines to assist developers in utilizing the API effectively.
IncrementAttribute: Adding a method that allows you to increment the value of a numeric attribute by a specified amount, simplifying operations such as score tracking or progress updates.
UpdateAttribute: Introducing a function that enables you to update the value of an existing attribute, providing flexibility in modifying attribute values without the need to remove and reassign them.
RemoveAttribute: Implementing a method to remove a specific attribute from an instance, giving you more control over attribute management and cleanup.
Possible Future Additions:
SetInstancesAttributes: Considering the addition of a function that allows you to set multiple attributes for multiple instances simultaneously, further streamlining attribute assignment when dealing with groups of objects.
GetAddedAttribute and GetRemovedAttribute: Exploring the possibility of functions that return the attributes added or removed during attribute assignments or removals, providing useful information for tracking changes.
Feel free to suggest additional features or improvements to the AttributeService! Your feedback is valuable in shaping the future development of this API.
I personally use Attributes for read-only purposes. Instead, I store values in tables and reflect them onto attributes. This approach allows me to utilize Attributes for viewing player data, similar to printing, while keeping dependencies manageable. However, I understand that setting instances as attributes can be useful in certain scenarios and would consider utilizing it once Roblox fully supports tables and instances.
For sending complex data over the network, I recommend using Remotes as they offer optimized and efficient performance. Attributes perform similarly to ValueBases and Properties, so Remotes are the preferred method when dealing with complex data transfer.
You should make something like allow tables using HTTPService:JSONEncode() and Decode to save them as a string; but the module it self would KNOW that it was supposed to be a table and would convert them into a table using JSONDecode(), I was gonna make a module for that but just didn’t want to anymore; (don’t try detecting it because there’s {}, try to have an ID on the name or something similar if you were to do that;)
please let me know if I have done FindFirstChild and WaitForChild correctly~ thanks!
reply to @LucasTutoriaisSaimo
@LucasTutoriaisSaimo, thank you for the suggestion however, Roblox might support tables (instances, CFrame, etc) later on so we don’t have to make our own Codec for it.
While I think your WaitForChildWithAttribute function would work, I do think there is room for improvement and optimization.
function AttributeService:WaitForChildWithAttribute(parent, attribute, timeOut)
assert(typeof(parent) == "Instance")
assert(typeof(attribute) == "string")
assert(typeof(timeOut) == "number" or timeOut == nil)
local Children = parent:GetChildren()
for _,Child in pairs(Children) do
if Child:GetAttribute(attribute) then
return Child
end
end
-- if the child already exists, no need to run any of this other code.
local START_TIME = tick()
local YIELD_UNTIL = (timeOut and timeOut + START_TIME or math.huge)
local YIELD_WARN = timeOut == nil and START_TIME + 10 or nil
-- YIELD_WARN provides a 10 second wait before it warns the user about infinite
-- possible yield, given that timeOut is nil.
local AttributeChangedConnections = {}
-- Table of connections so we can disconnect them later
local FOUND_CHILD
for _,Child in pairs(Children) do
local connection = child.AttributeChanged:Connect(function(newAttribute)
if newAttribute == attribute then
FOUND_CHILD = Child
end
end)
table.insert(AttributeChangedConnections,connection)
end
-- This creates the list of connections that wait for the attribute to be added.
-- I considered adding a :GetAttribute() call to check if it's nil, but that should
-- never be the case since we've already checked if they had it, so a change
-- would have to be a non nill value
local ChildAdded = parent.ChildAdded:Connect(function(child)
if child:GetAttribute(attribute) then
FOUND_CHILD = child
else
local connection = child.AttributeChanged:Connect(function(newAttribute)
if newAttribute == attribute then
FOUND_CHILD = Child
end
end)
table.insert(AttributeChangedConnections,connection)
-- same as above, if the new child has no value for the attribute, it waits
-- for a change to occur.
end
end)
-- Instead of running parent:GetChildren() every loop, instead we check if the child was
-- found by ChildAdded, saving resources that way.
-- I also want to point out that I'm using :Connect() instead of :Wait() because a
-- :Wait() call can't be canceled, meaning you can't specify how long it'll wait for.
while not FOUND_CHILD and tick() =< YIELD_UNTIL do
-- this will break out when child is found, or if it yields too long
if YIELD_WARN and tick() => YIELD_WARN then
warn("Infinite yield possible on WaitForChildWithAttribute:", parent.Name, attribute)
YIELD_WARN = nil
-- from what I could tell, the original function would never actually warn the user,
-- so I did this to remedy that.
end
Heartbeat:Wait()
end
ChildAdded:Disconnect()
for i,v in pairs(AttributeChangedConnections) do
v:Disconnect()
end
-- Disconnecting the ChildAdded connection, to preventing a memory leak.
-- Also the for loop disconnects the AttributeChanged functions
return FOUND_CHILD -- Return child, if found.
end
I may have messed something up, or there may be a faster way to pull this off, but I’m pretty sure this will work. Though I’d like to point out that the “GetAttribute returns 0 values instead of nil when no value has been assigned” bug will break this and all other “find child with with attribute” function until it’s fixed.
Not sure if you’re accepting contributions or if this is even considered useful but I created a function that returns every instance in the game with a certain attribute, similar to :GetTagged with CollectionService.
I suppose it would be similar to GetDescendantsWithAttribute but with the entire game.
function AttributeService:GetAttributed(attribute)
local services = {}
for i,v in pairs(game:GetChildren()) do
pcall(function()
services[#services + 1] = game:GetService(v.Name)
end)
end
local tagged = {}
for i,service in pairs(services) do
pcall(function()
for i2,v2 in pairs(service:GetDescendants()) do
local tag = v2:GetAttribute(attribute)
if tag ~= nil then
tagged[#tagged + 1] = v2
end
end
end)
end
return tagged
end
I also have one that returns every attribute in the game:
function AttributeService:GetAllAttributes(parent)
parent = parent or game
local services = {}
for i,v in pairs(game:GetChildren()) do
pcall(function()
services[#services + 1] = game:GetService(v.Name)
end)
end
if parent == game then
local tags = {}
for i,service in pairs(services) do
pcall(function()
attributesInService = self:GetAllAttributes(service)
end)
for i,v in pairs(attributesInService) do
if not table.find(tags, v) then
tags[#tags + 1] = v
end
end
end
return tags
else
local attributesToReturn = {}
for i,v in pairs(parent:GetDescendants()) do
local currentAttributes = v:GetAttributes()
for i,v in pairs(currentAttributes) do
if not table.find(attributesToReturn, v) then
attributesToReturn[#attributesToReturn + 1] = i
end
end
end
return attributesToReturn
end
end
Thank you both for your contributions however while features and performance is important I’d like to keep the module as clean and simple as possible
#1 Reply to NotAPorgu
@NotAPorgu your WaitForChildWithAttribute implementation wouldn’t work well because it behaves like WaitForChild which we can’t do that, it will not return if an already existing child had an Attribute added to it.
Oh right I forgot about that, I doubt it would be hard to update it to add an .AttributeChanged connection to it. I can write an updated version if you want me to.
EDIT: I just updated my original post to include the AttributeChanged connection
I have made changes to the module and now it includes:
The ability to add Instance as an Attribute
IsSupportedType(value)
IsSpecialType(value)
LuaU type checking
run time type checking using t
Instances are added to a Table and also tagged with a GUID and that GUID is also added as an Attribute, this is generally for replication and in some cases when the Instance.Parent is nil.
Although I do believe it is better to use Remotes for complex tables, I will add support for JSON valid tables for those who need it.
I don’t know if you quit working on this however it would be nice if you could implement a method like Debris:AddItem(instance,time) but works with attributes.
Changed name from AttributeService to AttributeUtil
Added CFrame support
Added Table support for JSON valid values only: will automatically filter out invalid values
Added a new Codec
Added a new TestEz spec.lua file
How Instance works:
Currently all SPECIAL_TYPES are given a Header during Encoding to identify what type an Attribute is while Decoding
How Instance serialization works:
Currently all Instances in the game are given an Attribute name “GUID” with a GUID as it’s value for reference and this value is also assign as a collection tag to the Instance for identification.
Collection Tags will not replicate on some Services and Instances that are nil to the client or vice versa
@Neotrinax that’s not really in the scope of this module, you can easily program something like that yourself using task.delay to remove an Attribute
Fix Encoding: filter now works correctly for Nested Tables
Set Attributes will make sure that Attribute name is valid (Names must only use alphanumeric characters and underscore, No spaces or unique symbols are allowed)
Table memory address is now shown in a Table Attribute Value