ReeseR
ReeseR4mo ago

Best way to sync data on a table?

I'm creating something similar to a bowling scoresheet: Each row is a different player and each column contains scores for each round. Right now I'm thinking about using a couple realtime arrays much like you would use 2 lists to store the data. However, the model for the inner array only contains a single int (and the outer one contains only an realtime array). I'm also not sure a RealtimeSet would work since the data isn't ordered? I hope I'm over-complicating this. Is there a better way to sync this kind of data?
20 Replies
kapa.ai
kapa.ai4mo ago
Hi @ReeseR. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- For your use case, a RealtimeDictionary might be a better fit. According to the documentation (https://normcore.io/documentation/room/collections#non-transactional-collections), a RealtimeDictionary is commonly used to represent a model that can have fields that are dynamically added or removed at runtime. You could create a ScoreModel and store instances of it in a RealtimeDictionary using each player's clientID as the key. The ScoreModel could contain an array or list of integers representing the scores for each round. This way, you can easily add or remove scores for each player and keep everything in sync. Here's a simplified example of what the ScoreModel might look like:
public class ScoreModel : RealtimeModel {
// The scores for each round.
private RealtimeArray<int> _scores;

// Get the scores.
public RealtimeArray<int> Scores {
get { return _scores; }
}
}
public class ScoreModel : RealtimeModel {
// The scores for each round.
private RealtimeArray<int> _scores;

// Get the scores.
public RealtimeArray<int> Scores {
get { return _scores; }
}
}
And here's how you might use it with a RealtimeDictionary:
// Create a new RealtimeDictionary.
RealtimeDictionary<uint, ScoreModel> scoreDictionary = new RealtimeDictionary<uint, ScoreModel>();

// Add a new ScoreModel for a player.
ScoreModel playerScore = new ScoreModel();
scoreDictionary.Add(playerID, playerScore);

// Update a player's score.
playerScore.Scores.Add(score);
// Create a new RealtimeDictionary.
RealtimeDictionary<uint, ScoreModel> scoreDictionary = new RealtimeDictionary<uint, ScoreModel>();

// Add a new ScoreModel for a player.
ScoreModel playerScore = new ScoreModel();
scoreDictionary.Add(playerID, playerScore);

// Update a player's score.
playerScore.Scores.Add(score);
Remember, the RealtimeDictionary and ScoreModel would need to be properly set up to work with Normcore's synchronization system. This is just a simplified example to illustrate the concept. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai4mo ago
<@806620207447146517> kapa.ai is still learning and improving, please let us know how it did by reacting below
ReeseR
ReeseROP4mo ago
RealtimeArray's can't be integers, only models? At least from what I'm reading in the docs: "RealtimeArray is a special model type that holds a sequential list of models that can be modified at runtime."
Mechabit
Mechabit4mo ago
make a model with an int property and put that model into a realtime array you can nest arrays this way
ReeseR
ReeseROP3mo ago
Ok, I've made a table using nested realtime models. Since I need to actively update each score, I used a RealtimeDictionary. How do I get the model to update? There is no "RowDidChange" with Realtime dictionaries, and I need to know the specific cell in order to update the right spot on the visible table. Here's the structure:
No description
No description
No description
Mechabit
Mechabit3mo ago
add another property to the model that says what cell and row was changed
ReeseR
ReeseROP3mo ago
@Normcore Assistant I'm trying to auto-generate a table of scores. Currently, it doesn't work with RealtimeInstantiate. When making this table of scores, does every cell need a realtime sync component on it? Or is there a way to do it with only a sync on the parent object?
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- In Normcore, parent-child relationships are not automatically synchronized. As mentioned in the documentation (https://normcore.io/documentation/realtime/common-questions#can-i-reparent-a-game-object-with-a-realtimeview-or-realtimecomponent-on-it), Normcore does not synchronize parent relationships between objects. If the new parent gets destroyed, it will take the RealtimeView and its components with it, which can result in desyncs between your clients. Therefore, if you want to synchronize each cell in your table of scores, you would need to add a RealtimeComponent to each cell. This could be a custom RealtimeComponent that you create to handle the specific data you need to sync for each cell. However, if the data you're trying to sync is simply the scores for each player, you might not need to use RealtimeInstantiate or synchronize each cell individually. Instead, you could use a RealtimeDictionary or a StringKeyDictionary to store and sync the scores, as suggested in the documentation (https://normcore.io/documentation/realtime/common-questions#im-trying-to-store-my-players-health-in-a-realtimecomponent-on-my-avatar-how-can-i-let-other-players-modify-this-values-or-others). You could then update your table locally based on the data in the dictionary. This would avoid the need to manage a large number of RealtimeComponents and could make your implementation simpler and more efficient. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@806620207447146517> kapa.ai is still learning and improving, please let us know how it did by reacting below
ReeseR
ReeseROP3mo ago
@Normcore Assistant When you say "Update you table locally" do you mean update every frame? Because checking every cell on a table every single frame sounds like it takes a lot of performance. Is there a better way to do it?
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- When I say "update your table locally", I don't necessarily mean every frame. Instead, you can update it whenever there's a change in the data you're tracking. In the context of Normcore, you can use event handlers to listen for changes in your RealtimeDictionary or StringKeyDictionary. When a change event is triggered, you can then update the corresponding cell in your table. This way, you're not checking every cell every frame, but only updating when necessary. Here's a simplified example of how you might set up an event handler for a RealtimeDictionary:
// Assuming scoreDictionary is your RealtimeDictionary
scoreDictionary.OnValueChanged += HandleValueChanged;

void HandleValueChanged(uint key, ScoreModel oldValue, ScoreModel newValue) {
// Update the corresponding cell in your table here
}
// Assuming scoreDictionary is your RealtimeDictionary
scoreDictionary.OnValueChanged += HandleValueChanged;

void HandleValueChanged(uint key, ScoreModel oldValue, ScoreModel newValue) {
// Update the corresponding cell in your table here
}
In this example, HandleValueChanged would be called whenever a value in scoreDictionary changes. The key parameter would be the player's clientID, and newValue would be the new score. You could then use this information to update the corresponding cell in your table. Remember, this is a simplified example and you would need to adapt it to fit your specific use case and to work with Normcore's synchronization system. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@806620207447146517> kapa.ai is still learning and improving, please let us know how it did by reacting below
ReeseR
ReeseROP3mo ago
Sigh... Ok. Lets try a full context sentence... @Normcore Assistant I'm making a table of scores using nested Realtime Dictionaries. Due to the way the table is currently set up, I do not want to add a Sync component to every cell. Since there is no "OnValueChanged" event for dictionaries, how can I change the text on any cell without looping through the whole table every frame?
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- You can use the modelAdded, modelReplaced, and modelRemoved events provided by the RealtimeDictionary to listen for changes and update your table accordingly. Here's a simplified example of how you might set up these event handlers:
// Assuming scoreDictionary is your RealtimeDictionary
scoreDictionary.modelAdded += HandleModelAdded;
scoreDictionary.modelReplaced += HandleModelReplaced;
scoreDictionary.modelRemoved += HandleModelRemoved;

void HandleModelAdded(uint key, ScoreModel model) {
// A new model was added to the dictionary.
// Update the corresponding cell in your table here.
}

void HandleModelReplaced(uint key, ScoreModel oldModel, ScoreModel newModel) {
// A model in the dictionary was replaced.
// Update the corresponding cell in your table here.
}

void HandleModelRemoved(uint key, ScoreModel model) {
// A model was removed from the dictionary.
// Update the corresponding cell in your table here.
}
// Assuming scoreDictionary is your RealtimeDictionary
scoreDictionary.modelAdded += HandleModelAdded;
scoreDictionary.modelReplaced += HandleModelReplaced;
scoreDictionary.modelRemoved += HandleModelRemoved;

void HandleModelAdded(uint key, ScoreModel model) {
// A new model was added to the dictionary.
// Update the corresponding cell in your table here.
}

void HandleModelReplaced(uint key, ScoreModel oldModel, ScoreModel newModel) {
// A model in the dictionary was replaced.
// Update the corresponding cell in your table here.
}

void HandleModelRemoved(uint key, ScoreModel model) {
// A model was removed from the dictionary.
// Update the corresponding cell in your table here.
}
In these examples, HandleModelAdded, HandleModelReplaced, and HandleModelRemoved would be called whenever a model is added, replaced, or removed in scoreDictionary. You could then use this information to update the corresponding cell in your table. Remember, this is a simplified example and you would need to adapt it to fit your specific use case and to work with Normcore's synchronization system. You can find more information about these events in the RealtimeDictionary documentation (https://normcore.io/documentation/reference/classes/Normal.Realtime.Serialization.RealtimeDictionary%601). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@806620207447146517> kapa.ai is still learning and improving, please let us know how it did by reacting below
ReeseR
ReeseROP3mo ago
😮‍💨 Except, unless I did something wrong, "HandleModelReplaced" doesn't call within nested dictionaries... Ok, fine. I've added a component to each cell that checks its data on the model every single frame. I really don't like this solution. If someone knows a better way let me know.
pir8x
pir8x3mo ago
I don't know if this fits your use case, but we did a thing where instead of storing/syncing player score totals, we sync a log of score events in an unordered RealtimeSet.
event 0 { player = 1, score = 1 }
event 1 { player = 2, score = 5 }
event 2 { player = 2, score = 2 }
event 0 { player = 1, score = 1 }
event 1 { player = 2, score = 5 }
event 2 { player = 2, score = 2 }
Everyone's local arrays only get updated on the ModelAdded event. Then we calculate the local scores from the log as required:
List<scoreEvents> player2Scores = localScoreEvents.FindAll(e => e.player == 2)

player2Score = 0
foreach(scoreEvent in player2Scores)
{
player2Score += scoreEvent.score
}

// player2Score is 7
List<scoreEvents> player2Scores = localScoreEvents.FindAll(e => e.player == 2)

player2Score = 0
foreach(scoreEvent in player2Scores)
{
player2Score += scoreEvent.score
}

// player2Score is 7
(Our game is turn-based and has infrequent score updates.) (Pseudo-code - our scoring system has more dimensions and data in the score "ledger", but hopefully you get the idea.) ...for i.e. a bowling score sheet your event could be { player, frame, delivery, pins }
ReeseR
ReeseROP3mo ago
Yeah, unfortunately mine isn't turn-based. The data syncs just fine, but I need to trigger an event on data sync that updates the scoreboard text. Still not sure there's a way to do that with realtime dictionaries. So, instead I have a component on every text object that checks a player's score every single frame and updates the text.
pir8x
pir8x3mo ago
I think it would still work for you. In our model, we never update any of the list elements - we just add score events (net change). RealtimeSet has an event for when a model is added, which the clients' local sim then uses to calculate/update the score locally.
ReeseR
ReeseROP3mo ago
Hmm... When I get a chance I'll play around with the model added event and see what happens. Might be a while, but I'll let you know how it goes.