SoundGuy2
SoundGuy27mo ago

Button press on one client

What is the right way to sync a button press form one client to the rest, should I use an RPC event?
Solution:
you should give up on the rpc recipe, instead sync the the menu state as an int and use the model changed event to update the menu on clients
Jump to solution
27 Replies
kapa.ai
kapa.ai7mo ago
Hi @SoundGuy2. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- While RPC messages are available in Normcore, they are generally discouraged for use in most cases due to the difficulty they present in maintaining consistent synchronization across clients. Any state that is modified in response to an RPC message will need to be manually synchronized by your own code, which can lead to desyncs and bugs that are hard to reproduce and debug (source (https://normcore.io/documentation/guides/recipes/rpc-events#preface)). Instead, Normcore recommends using the room's datastore for synchronizing state across clients. When you make a change to the datastore, it is automatically replicated to all connected clients. This means that if you need to sync a button press from one client to the rest, you should change its state in the datastore, and the datastore will automatically notify all clients connected to the room so they can update their world to match (source (https://normcore.io/documentation/architecture/client#datastore)). However, if you absolutely need to use an RPC-like structure, Normcore provides a recipe for creating an RPC-like event message that can be fired by anyone in the room. This recipe involves creating a model that holds all the values you'd typically send in an RPC message and an integer that you can increment every time you want to trigger an event. You can then create a RealtimeComponent that uses this model to call a method on the button press whenever the event fires (source (https://normcore.io/documentation/guides/recipes/rpc-events#recipe)). Here's an example of how you can create such a model and RealtimeComponent:
[RealtimeModel]
public class ButtonPressEventModel {
[RealtimeProperty(1, true)] private int _trigger;
[RealtimeProperty(2, true)] private int _senderID;
// Used to fire an event on all clients
public void FireEvent(int senderID) {
this.trigger++;
this.senderID = senderID;
}

// An event that consumers of this model can subscribe to in order to respond to the event
public delegate void EventHandler(int senderID);
public event EventHandler eventDidFire;

// A RealtimeCallback method that fires whenever we read any values from the server
[RealtimeCallback(RealtimeModelEvent.OnDidRead)]
private void DidRead() {
if (eventDidFire != null && trigger != 0)
eventDidFire(senderID);
}
}

public class ButtonPressEvent : RealtimeComponent<ButtonPressEventModel> {
// When we connect to a room server, we'll be given an instance of our model to work with.
protected override void OnRealtimeModelReplaced(ButtonPressEventModel previousModel, ButtonPressEventModel currentModel) {
if (previousModel != null) {
// Unsubscribe from events on the old model.
previousModel.eventDidFire -= EventDidFire;
}
if (currentModel != null) {
// Subscribe to events on the new model
currentModel.eventDidFire += EventDidFire;
}
}

// A public method we can use to fire the event
public void Press() {
model.FireEvent(realtime.clientID);
}

// Called whenever our event fires
private void EventDidFire(int senderID) {
// Do something in response to the button press event
}
}
[RealtimeModel]
public class ButtonPressEventModel {
[RealtimeProperty(1, true)] private int _trigger;
[RealtimeProperty(2, true)] private int _senderID;
// Used to fire an event on all clients
public void FireEvent(int senderID) {
this.trigger++;
this.senderID = senderID;
}

// An event that consumers of this model can subscribe to in order to respond to the event
public delegate void EventHandler(int senderID);
public event EventHandler eventDidFire;

// A RealtimeCallback method that fires whenever we read any values from the server
[RealtimeCallback(RealtimeModelEvent.OnDidRead)]
private void DidRead() {
if (eventDidFire != null && trigger != 0)
eventDidFire(senderID);
}
}

public class ButtonPressEvent : RealtimeComponent<ButtonPressEventModel> {
// When we connect to a room server, we'll be given an instance of our model to work with.
protected override void OnRealtimeModelReplaced(ButtonPressEventModel previousModel, ButtonPressEventModel currentModel) {
if (previousModel != null) {
// Unsubscribe from events on the old model.
previousModel.eventDidFire -= EventDidFire;
}
if (currentModel != null) {
// Subscribe to events on the new model
currentModel.eventDidFire += EventDidFire;
}
}

// A public method we can use to fire the event
public void Press() {
model.FireEvent(realtime.clientID);
}

// Called whenever our event fires
private void EventDidFire(int senderID) {
// Do something in response to the button press event
}
}
Please note that this is a simplified example and you may need to adjust it to fit your specific needs. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai7mo ago
<@1081953198014734357> kapa.ai is still learning and improving, please let us know how it did by reacting below
SoundGuy2
SoundGuy27mo ago
@Normcore Assistant can you give me the following core - I am pressing a button with an int number in one client a function is called in another client with that number in the other clients
kapa.ai
kapa.ai7mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Sure, you can achieve this by using Normcore's RealtimeModel and RealtimeComponent. Here's an example of how you can create a model and a RealtimeComponent for a button press event that includes an integer value:
[RealtimeModel]
public partial class ButtonPressEventModel {
[RealtimeProperty(1, true)] private int _trigger;
[RealtimeProperty(2, true)] private int _senderID;
[RealtimeProperty(3, true)] private int _buttonNumber;

// Used to fire an event on all clients
public void FireEvent(int senderID, int buttonNumber) {
this.trigger++;
this.senderID = senderID;
this.buttonNumber = buttonNumber;
}

// An event that consumers of this model can subscribe to in order to respond to the event
public delegate void EventHandler(int senderID, int buttonNumber);
public event EventHandler eventDidFire;

// A RealtimeCallback method that fires whenever we read any values from the server
[RealtimeCallback(RealtimeModelEvent.OnDidRead)]
private void DidRead() {
if (eventDidFire != null && trigger != 0)
eventDidFire(senderID, buttonNumber);
}
}

public class ButtonPressEvent : RealtimeComponent<ButtonPressEventModel> {
// When we connect to a room server, we'll be given an instance of our model to work with.
protected override void OnRealtimeModelReplaced(ButtonPressEventModel previousModel, ButtonPressEventModel currentModel) {
if (previousModel != null) {
// Unsubscribe from events on the old model.
previousModel.eventDidFire -= EventDidFire;
}
if (currentModel != null) {
// Subscribe to events on the new model
currentModel.eventDidFire += EventDidFire;
}
}

// A public method we can use to fire the event
public void Press(int buttonNumber) {
model.FireEvent(realtime.clientID, buttonNumber);
}
// Called whenever our event fires
private void EventDidFire(int senderID, int buttonNumber) {
// Do something in response to the button press event
}
}
[RealtimeModel]
public partial class ButtonPressEventModel {
[RealtimeProperty(1, true)] private int _trigger;
[RealtimeProperty(2, true)] private int _senderID;
[RealtimeProperty(3, true)] private int _buttonNumber;

// Used to fire an event on all clients
public void FireEvent(int senderID, int buttonNumber) {
this.trigger++;
this.senderID = senderID;
this.buttonNumber = buttonNumber;
}

// An event that consumers of this model can subscribe to in order to respond to the event
public delegate void EventHandler(int senderID, int buttonNumber);
public event EventHandler eventDidFire;

// A RealtimeCallback method that fires whenever we read any values from the server
[RealtimeCallback(RealtimeModelEvent.OnDidRead)]
private void DidRead() {
if (eventDidFire != null && trigger != 0)
eventDidFire(senderID, buttonNumber);
}
}

public class ButtonPressEvent : RealtimeComponent<ButtonPressEventModel> {
// When we connect to a room server, we'll be given an instance of our model to work with.
protected override void OnRealtimeModelReplaced(ButtonPressEventModel previousModel, ButtonPressEventModel currentModel) {
if (previousModel != null) {
// Unsubscribe from events on the old model.
previousModel.eventDidFire -= EventDidFire;
}
if (currentModel != null) {
// Subscribe to events on the new model
currentModel.eventDidFire += EventDidFire;
}
}

// A public method we can use to fire the event
public void Press(int buttonNumber) {
model.FireEvent(realtime.clientID, buttonNumber);
}
// Called whenever our event fires
private void EventDidFire(int senderID, int buttonNumber) {
// Do something in response to the button press event
}
}
In this example, when a button is pressed on one client, the Press method is called with the button number as an argument. This fires an event on all clients, triggering the EventDidFire method on each client with the sender's ID and the button number as arguments. You can replace the comment in the EventDidFire method with the function you want to call on the other clients. Please note that this is a simplified example and you may need to adjust it to fit your specific needs. You can find more information about creating a RealtimeModel and RealtimeComponent in the Normcore documentation (https://normcore.io/documentation/guides/recipes/rpc-events#recipe). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai7mo ago
<@1081953198014734357> kapa.ai is still learning and improving, please let us know how it did by reacting below
SoundGuy2
SoundGuy27mo ago
@Tom why isn't this code nice like in the tickets you opened?
Towellie
Towellie7mo ago
I was just looking at that myself discord has character limits and it seems the length spanned multiple messages
[RealtimeModel]
public partial class ButtonPressEventModel {
[RealtimeProperty(1, true)] private int _trigger;
[RealtimeProperty(2, true)] private int _senderID;
[RealtimeProperty(3, true)] private int _buttonNumber;

// Used to fire an event on all clients
public void FireEvent(int senderID, int buttonNumber) {
this.trigger++;
this.senderID = senderID;
this.buttonNumber = buttonNumber;
}

// An event that consumers of this model can subscribe to in order to respond to the event
public delegate void EventHandler(int senderID, int buttonNumber);
public event EventHandler eventDidFire;

// A RealtimeCallback method that fires whenever we read any values from the server
[RealtimeCallback(RealtimeModelEvent.OnDidRead)]
private void DidRead() {
if (eventDidFire != null && trigger != 0)
eventDidFire(senderID, buttonNumber);
}
}

public class ButtonPressEvent : RealtimeComponent<ButtonPressEventModel> {
// When we connect to a room server, we'll be given an instance of our model to work with.
protected override void OnRealtimeModelReplaced(ButtonPressEventModel previousModel, ButtonPressEventModel currentModel) {
if (previousModel != null) {
// Unsubscribe from events on the old model.
previousModel.eventDidFire -= EventDidFire;
}
if (currentModel != null) {
// Subscribe to events on the new model
currentModel.eventDidFire += EventDidFire;
}
}

// A public method we can use to fire the event
public void Press(int buttonNumber) {
model.FireEvent(realtime.clientID, buttonNumber);
}
// Called whenever our event fires
private void EventDidFire(int senderID, int buttonNumber) {
// Do something in response to the button press event
}
}
[RealtimeModel]
public partial class ButtonPressEventModel {
[RealtimeProperty(1, true)] private int _trigger;
[RealtimeProperty(2, true)] private int _senderID;
[RealtimeProperty(3, true)] private int _buttonNumber;

// Used to fire an event on all clients
public void FireEvent(int senderID, int buttonNumber) {
this.trigger++;
this.senderID = senderID;
this.buttonNumber = buttonNumber;
}

// An event that consumers of this model can subscribe to in order to respond to the event
public delegate void EventHandler(int senderID, int buttonNumber);
public event EventHandler eventDidFire;

// A RealtimeCallback method that fires whenever we read any values from the server
[RealtimeCallback(RealtimeModelEvent.OnDidRead)]
private void DidRead() {
if (eventDidFire != null && trigger != 0)
eventDidFire(senderID, buttonNumber);
}
}

public class ButtonPressEvent : RealtimeComponent<ButtonPressEventModel> {
// When we connect to a room server, we'll be given an instance of our model to work with.
protected override void OnRealtimeModelReplaced(ButtonPressEventModel previousModel, ButtonPressEventModel currentModel) {
if (previousModel != null) {
// Unsubscribe from events on the old model.
previousModel.eventDidFire -= EventDidFire;
}
if (currentModel != null) {
// Subscribe to events on the new model
currentModel.eventDidFire += EventDidFire;
}
}

// A public method we can use to fire the event
public void Press(int buttonNumber) {
model.FireEvent(realtime.clientID, buttonNumber);
}
// Called whenever our event fires
private void EventDidFire(int senderID, int buttonNumber) {
// Do something in response to the button press event
}
}
here you go
SoundGuy2
SoundGuy27mo ago
@Normcore Assistant does you last example use RPC or not?
kapa.ai
kapa.ai7mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- The example provided does not use RPC in the traditional sense. Instead, it uses Normcore's RealtimeModel and RealtimeComponent to achieve a similar effect. In this example, the button press event is represented as a change in the state of a RealtimeModel. When the button is pressed, the FireEvent method is called, which increments the _trigger property and sets the _senderID and _buttonNumber properties. This change in the model's state is automatically synchronized across all clients connected to the room. When a client receives the updated model from the server, it triggers the DidRead method, which in turn fires the eventDidFire event. This event is handled by the EventDidFire method in the RealtimeComponent, which can then call any function you want with the sender's ID and the button number as arguments. This approach provides a way to send messages between clients that is similar to RPC, but it avoids some of the synchronization issues that can occur with traditional RPC messages (source (https://normcore.io/documentation/guides/recipes/rpc-events#preface)). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai7mo ago
<@1081953198014734357> kapa.ai is still learning and improving, please let us know how it did by reacting below
SoundGuy2
SoundGuy27mo ago
ok thanks @Normcore Assistant
kapa.ai
kapa.ai7mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- You're welcome! If you have any more questions about Normcore or VR development, feel free to ask. Happy coding! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai7mo ago
<@1081953198014734357> kapa.ai is still learning and improving, please let us know how it did by reacting below
SoundGuy2
SoundGuy27mo ago
@Normcore Assistant can the realtimeModel and component in the last example can be in the same Cs file or do i need to put the model in a separate cs file like in the other examples
kapa.ai
kapa.ai7mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Yes, the RealtimeModel and the RealtimeComponent can be in the same C# file. They are two separate classes and can coexist in the same file without any issues. However, for organizational purposes and to maintain clean code, it's often recommended to keep them in separate files, especially if they start to get large or complex. But this is not a requirement, just a suggestion for best practices. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------