Compare commits

..

No commits in common. "a5664dea9d479c3bc1bec71fbcec5efd76a42f0b" and "620b7e52734a790bb05075546fc29a460430ee97" have entirely different histories.

10 changed files with 154 additions and 172 deletions

View File

@ -25,7 +25,7 @@ namespace RoboidControl {
}; };
} }
public override void Update(bool recurse = true) { public override void Update(ulong currentTimeMs, bool recurse = true) {
// The left wheel turns forward when nothing is touched on the right side // The left wheel turns forward when nothing is touched on the right side
// and turn backward when the roboid hits something on the right // and turn backward when the roboid hits something on the right
@ -40,7 +40,7 @@ namespace RoboidControl {
this.SetWheelVelocity(leftWheelSpeed, rightWheelSpeed); this.SetWheelVelocity(leftWheelSpeed, rightWheelSpeed);
base.Update(recurse); base.Update(currentTimeMs, recurse);
} }
} }

View File

@ -33,7 +33,7 @@ namespace RoboidControl {
}; };
} }
public override void Update(bool recurse = true) { public override void Update(ulong currentTimeMs, bool recurse = true) {
// The left wheel turns forward when nothing is touched on the right side // The left wheel turns forward when nothing is touched on the right side
// and turn backward when the roboid hits something on the right // and turn backward when the roboid hits something on the right
@ -48,7 +48,7 @@ namespace RoboidControl {
this.SetWheelAngularVelocity(leftWheelVelocity, rightWheelVelocity); this.SetWheelAngularVelocity(leftWheelVelocity, rightWheelVelocity);
base.Update(recurse); base.Update(currentTimeMs, recurse);
} }
} }

View File

@ -15,13 +15,10 @@ namespace RoboidControl {
/// It is used as a basis for the local participant, but also as a reference to remote participants. /// It is used as a basis for the local participant, but also as a reference to remote participants.
public class Participant { public class Participant {
#region Init
/// <summary>
/// Create a generic participant
/// </summary>
private Participant() { private Participant() {
Thing.CreateRoot(this); this.root = Thing.CreateRoot(this);
this.root.name = "Root";
this.Add(this.root);
} }
/// <summary> /// <summary>
@ -29,60 +26,47 @@ namespace RoboidControl {
/// </summary> /// </summary>
/// <param name="ipAddress">The IP address of the participant</param> /// <param name="ipAddress">The IP address of the participant</param>
/// <param name="port">The UDP port of the participant</param> /// <param name="port">The UDP port of the participant</param>
/// <remarks>This does not belong here, it should move to ParticipantUDP or something like that in the future
public Participant(string ipAddress, int port, Participant localParticipant = null) { public Participant(string ipAddress, int port, Participant localParticipant = null) {
this.ipAddress = ipAddress; this.ipAddress = ipAddress;
this.port = port; this.port = port;
Thing.CreateRoot(this); this.root = Thing.CreateRoot(this);
this.root.name = "Root";
if (localParticipant != null) if (localParticipant != null)
this.udpClient = localParticipant.udpClient; this.udpClient = localParticipant.udpClient;
} }
/// <summary>
/// The local participant for this application
/// </summary>
public static Participant localParticipant = new(); public static Participant localParticipant = new();
/// <summary> public static void ReplaceLocalParticipant(Participant participant) {
/// Replace the local participant Participant.localParticipant = participant;
/// </summary>
/// <param name="newParticipant">The new local participant</param>
public static void ReplaceLocalParticipant(Participant newParticipant) {
Participant.localParticipant = newParticipant;
} }
#endregion Init public Thing root = null;
#region Properties
/// <summary> /// <summary>
/// The Ip Address of a participant. When the participant is local, this contains 0.0.0.0 /// The Ip Address of a participant. When the participant is local, this contains 0.0.0.0
/// </summary> /// </summary>
/// <remarks>This does not belong here, it should move to ParticipantUDP or something like that in the future
public string ipAddress = "0.0.0.0"; public string ipAddress = "0.0.0.0";
/// <summary> /// <summary>
/// The port number for UDP communication with the participant. This is 0 for isolated participants. /// The port number for UDP communication with the participant. This is 0 for isolated participants.
/// </summary> /// </summary>
/// <remarks>This does not belong here, it should move to ParticipantUDP or something like that in the future
public int port = 0; public int port = 0;
#if UNITY_5_3_OR_NEWER
/// <summary> /// <summary>
/// The udpClient for this participant /// A reference to the representation of the thing in Unity
/// </summary> /// </summary>
/// <remarks>This does not belong here, it should move to ParticipantUDP or something like that in the future [NonSerialized]
public Unity.Participant component = null;
#endif
public UdpClient udpClient = null; public UdpClient udpClient = null;
/// <summary> /// <summary>
/// The network Id to identify the participant /// he network Id to identify the participant
/// </summary> /// </summary>
public byte networkId = 0; public byte networkId = 0;
/// <summary>
/// The root thing for this participant
/// </summary>
public Thing root = null;
/// <summary> /// <summary>
/// The things managed by this participant /// The things managed by this participant
/// </summary> /// </summary>
@ -126,47 +110,26 @@ namespace RoboidControl {
this.things.Remove(thing); this.things.Remove(thing);
} }
#if UNITY_5_3_OR_NEWER
/// <summary>
/// A reference to the representation of the thing in Unity
/// </summary>
[NonSerialized]
public Unity.Participant component = null;
#endif
#endregion properties
#region Update #region Update
/// <summary> /// <summary>
/// Update all things for this participant /// Update all things for this participant
/// </summary> /// </summary>
public virtual void Update() { /// <param name="currentTimeMS">The current time in milliseconds (optional)</param>
public virtual void Update(ulong currentTimeMS = 0) {
int n = this.things.Count; int n = this.things.Count;
for (int ix = 0; ix < n; ix++) { for (int ix = 0; ix < n; ix++) {
Thing thing = this.things[ix]; Thing thing = this.things[ix];
if (thing != null) if (thing != null)
thing.Update(true); thing.Update(currentTimeMS, true);
} }
} }
/// <summary>
/// Event for a participant
/// </summary>
public class UpdateEvent { public class UpdateEvent {
/// <summary>
/// The type of event happened.
/// </summary>
/// This value is filled with the Ids of the communication messages.
public int messageId; // see the communication messages public int messageId; // see the communication messages
/// <summary>
/// The thing relevant fo the event
/// </summary>
public Thing thing; public Thing thing;
public Participant participant;
} }
/// <summary>
/// Queue containing events happened to this participant
/// </summary>
public ConcurrentQueue<UpdateEvent> updateQueue = new(); public ConcurrentQueue<UpdateEvent> updateQueue = new();
#endregion Update #endregion Update
@ -192,8 +155,6 @@ namespace RoboidControl {
#endregion Send #endregion Send
#region Participant Registry
/// <summary> /// <summary>
/// The collection of known participants. /// The collection of known participants.
/// </summary> /// </summary>
@ -252,7 +213,6 @@ namespace RoboidControl {
Participant.participants.Add(participant); Participant.participants.Add(participant);
} }
#endregion Participant Registery
} }
} }

View File

@ -83,9 +83,19 @@ namespace RoboidControl {
Participant.ReplaceLocalParticipant(this); Participant.ReplaceLocalParticipant(this);
} }
#endregion Init private static ParticipantUDP isolatedParticipant = null;
#region Properties /// <summary>
/// Isolated participant is used when the application is run without networking
/// </summary>
/// <returns>A participant without networking support</returns>
public static ParticipantUDP Isolated() {
if (isolatedParticipant == null)
isolatedParticipant = new ParticipantUDP(0);
return isolatedParticipant;
}
#endregion Init
/// <summary> /// <summary>
/// The name of the participant /// The name of the participant
@ -97,7 +107,6 @@ namespace RoboidControl {
/// </summary> /// </summary>
/// Isolated participants do not communicate with other participants /// Isolated participants do not communicate with other participants
public bool isIsolated = false; public bool isIsolated = false;
/// <summary> /// <summary>
/// The remote site when this participant is connected to a site /// The remote site when this participant is connected to a site
/// </summary> /// </summary>
@ -136,12 +145,14 @@ namespace RoboidControl {
Console.WriteLine($"Broadcast address: {this.broadcastIpAddress}"); Console.WriteLine($"Broadcast address: {this.broadcastIpAddress}");
} }
#endregion Properties
#region Update #region Update
public override void Update() { protected ulong nextPublishMe = 0;
ulong currentTimeMS = Thing.GetTimeMs(); protected ulong nextSendUpdate = 0;
public override void Update(ulong currentTimeMS = 0) {
if (currentTimeMS == 0)
currentTimeMS = Thing.GetTimeMs();
if (this.isIsolated == false) { if (this.isIsolated == false) {
if (this.publishIntervalMS > 0 && currentTimeMS > this.nextPublishMe) { if (this.publishIntervalMS > 0 && currentTimeMS > this.nextPublishMe) {
@ -159,9 +170,6 @@ namespace RoboidControl {
UpdateOtherThings(currentTimeMS); UpdateOtherThings(currentTimeMS);
} }
protected ulong nextPublishMe = 0;
protected ulong nextSendUpdate = 0;
protected virtual void UpdateMyThings(ulong currentTimeMS) { protected virtual void UpdateMyThings(ulong currentTimeMS) {
foreach (Thing thing in this.things) { foreach (Thing thing in this.things) {
if (thing == null) if (thing == null)
@ -177,7 +185,7 @@ namespace RoboidControl {
// Because when a thing creates a thing in the update, // Because when a thing creates a thing in the update,
// that new thing is not sent out (because of hierarchyChanged) // that new thing is not sent out (because of hierarchyChanged)
// before it is updated itself: it is immediatedly updated! // before it is updated itself: it is immediatedly updated!
thing.Update(false); thing.Update(currentTimeMS, false);
if (!(this.isIsolated || this.networkId == 0)) { if (!(this.isIsolated || this.networkId == 0)) {
if (thing.terminate) { if (thing.terminate) {
DestroyMsg destroyMsg = new(this.networkId, thing); DestroyMsg destroyMsg = new(this.networkId, thing);
@ -205,7 +213,7 @@ namespace RoboidControl {
if (participant == null || participant == this) if (participant == null || participant == this)
continue; continue;
participant.Update(); participant.Update(currentTimeMS);
if (this.isIsolated) if (this.isIsolated)
continue; continue;

View File

@ -72,7 +72,7 @@ namespace RoboidControl {
if (thing == null) if (thing == null)
continue; continue;
thing.Update(false); thing.Update(currentTimeMS, false);
if (this.isIsolated == false) { if (this.isIsolated == false) {
// Send to all other participants // Send to all other participants
@ -104,7 +104,8 @@ namespace RoboidControl {
// this.Send(sender, new NetworkIdMsg(sender.networkId)); // this.Send(sender, new NetworkIdMsg(sender.networkId));
sender.Send(new NetworkIdMsg(sender.networkId)); sender.Send(new NetworkIdMsg(sender.networkId));
UpdateEvent e = new() { UpdateEvent e = new() {
messageId = ParticipantMsg.Id messageId = ParticipantMsg.Id,
participant = sender
}; };
this.updateQueue.Enqueue(e); this.updateQueue.Enqueue(e);
} }

View File

@ -38,14 +38,73 @@ namespace RoboidControl {
#region Init #region Init
private Thing(Participant owner) {
this.type = Type.Root;
this.positionUpdated = true;
this.orientationUpdated = true;
this.hierarchyChanged = true;
this.owner = owner;
this.parent = null;
}
public static Thing CreateRoot(Participant owner) {
return new Thing(owner);
}
static Thing localRoot {
get {
Participant participant = Participant.localParticipant;
return participant.root;
}
}
/*
/// <summary> /// <summary>
/// Create a new Thing /// Create a new thing without communication abilities
/// </summary> /// </summary>
/// <param name="parent">(optional) The parent thing</param> /// <param name="thingType">The type of thing (can use \ref RoboidControl::Thing::Type "Thing.Type")</param>
/// <note>The owner will be the same as the owner of the parent thing, it will /// <param name="invokeEvent">Invoke a OnNewThing event when the thing has been created</param>
/// be Participant.LocalParticipant if the parent is not specified. A thing public Thing(byte thingType = Type.Undetermined, bool invokeEvent = true) : this(ParticipantUDP.Isolated(), thingType, 0, invokeEvent) {
/// without a parent will be connected to the root thing. }
/// </note>
/// <summary>
/// Create a new thing for a participant
/// </summary>
/// <param name="owner">The owning participant</param>
/// <param name="thingType">The type of thing (can use \ref RoboidControl::Thing::Type "Thing.Type")</param>
/// <param name="thingId">The ID of the thing, leave out or set to zero to generate an ID</param>
/// <param name="invokeEvent">Invoke a OnNewThing event when the thing has been created</param>
public Thing(Participant owner, byte thingType = Type.Undetermined, byte thingId = 0, bool invokeEvent = true) {
this.owner = owner;
this.id = thingId;
this.type = thingType;
if (this.owner != null)
this.owner.Add(this);
if (invokeEvent) {
Participant.UpdateEvent e = new() {
messageId = ThingMsg.id,
thing = this
};
this.owner.updateQueue.Enqueue(e);
}
}
*/
/// <summary>
/// Create a new child thing
/// </summary>
/// <param name="parent">The parent thing</param>
/// <param name="thingType">The type of thing (can use \ref RoboidControl::Thing::Type "Thing.Type")</param>
/// <param name="thingId">The ID of the thing, leave out or set to zero to generate an ID</param>
/// <param name="invokeEvent">Invoke a OnNewThing event when the thing has been created</param>
/// <note>The owner will be the same as the owner of the parent thing</note>
/*
public Thing(Thing parent, byte thingType = Type.Undetermined, byte thingId = 0, bool invokeEvent = true) : this(parent.owner, thingType, thingId, invokeEvent) {
this.parent = parent;
}
*/
public Thing(Thing parent = default) { public Thing(Thing parent = default) {
this.type = Type.Undetermined; this.type = Type.Undetermined;
@ -68,41 +127,12 @@ namespace RoboidControl {
this.owner.updateQueue.Enqueue(e); this.owner.updateQueue.Enqueue(e);
} }
/// <summary> // public static Thing CreateRemote(Participant owner, byte thingId) {
/// Constructor to create a root thing // Thing remoteThing = new(owner.root) {
/// </summary> // id = thingId
/// <param name="owner">The participant who will own this root thing</param> // };
/// <remarks>This function is private because CreateRoot() should be used instead</remarks> // return remoteThing;
private Thing(Participant owner) { // }
this.type = Type.Root;
this.name = "Root";
this.positionUpdated = true;
this.orientationUpdated = true;
this.hierarchyChanged = true;
this.owner = owner;
this.parent = null;
this.owner.Add(this);
}
/// <summary>
/// Create a root Thing for a participant
/// </summary>
/// <param name="owner">The participant who will own this root thing</param>
public static void CreateRoot(Participant owner) {
owner.root = new Thing(owner);
}
/// <summary>
/// The root thing for the local participant
/// </summary>
public static Thing localRoot {
get {
Participant participant = Participant.localParticipant;
return participant.root;
}
}
/// <summary> /// <summary>
/// Function which can be used to create components in external engines. /// Function which can be used to create components in external engines.
@ -117,13 +147,14 @@ namespace RoboidControl {
#endregion Init #endregion Init
/// <summary>
/// Terminated things are no longer updated
/// </summary>
public bool terminate = false; public bool terminate = false;
#region Properties #region Properties
/// <summary>
/// The participant owning this thing
/// </summary>
public Participant owner = null;
/// <summary> /// <summary>
/// The ID of this thing /// The ID of this thing
/// </summary> /// </summary>
@ -135,11 +166,6 @@ namespace RoboidControl {
/// This can be either a \ref RoboidControl::Thing::Type "Thing.Type" or a byte value for custom types. /// This can be either a \ref RoboidControl::Thing::Type "Thing.Type" or a byte value for custom types.
public byte type = Type.Undetermined; public byte type = Type.Undetermined;
/// <summary>
/// The participant owning this thing
/// </summary>
public Participant owner = null;
private string _name = ""; private string _name = "";
/// <summary> /// <summary>
/// The name of the thing /// The name of the thing
@ -160,9 +186,6 @@ namespace RoboidControl {
/// <summary> /// <summary>
/// An URL pointing to the location where a model of the thing can be found /// An URL pointing to the location where a model of the thing can be found
/// </summary> /// </summary>
/// <remarks>Although the roboid implementation is not dependent on the model,
/// the only official supported model formats are .png (sprite), .gltf and .glb
/// </remarks>
public string modelUrl { public string modelUrl {
get => _modelUrl; get => _modelUrl;
set { set {
@ -207,12 +230,6 @@ namespace RoboidControl {
} }
} }
/// <summary>
/// Indication whether this is a root thing
/// </summary>
public bool isRoot {
get => this == localRoot || this.parent == null;
}
/// <summary> /// <summary>
/// The children of this thing /// The children of this thing
@ -375,11 +392,32 @@ namespace RoboidControl {
#region Update #region Update
/// <summary>
/// Get the current time in milliseconds
/// </summary>
/// <returns>The current time in milliseconds</returns>
public static ulong GetTimeMs() {
#if UNITY_5_3_OR_NEWER
return (ulong)(UnityEngine.Time.time * 1000);
#else
return TimeManager.GetCurrentTimeMilliseconds();
#endif
}
/// <summary> /// <summary>
/// Update de state of the thing /// Update de state of the thing
/// </summary> /// </summary>
/// <param name="recurse">When true, this will Update the descendants recursively</param> /// <param name="recurse">When true, this will Update the descendants recursively</param>
public virtual void Update(bool recurse = false) { public void Update(bool recurse = false) {
Update(GetTimeMs(), recurse);
}
// #endif
/// <summary>
/// Update this thing
/// </summary>
/// <param name="currentTime">he current clock time in milliseconds; if this is zero, the current time is retrieved automatically</param>
/// <param name="recurse">When true, this will Update the descendants recursively</param>
public virtual void Update(ulong currentTimeMs, bool recurse = false) {
this.positionUpdated = false; this.positionUpdated = false;
this.orientationUpdated = false; this.orientationUpdated = false;
this.linearVelocityUpdated = false; this.linearVelocityUpdated = false;
@ -392,42 +430,19 @@ namespace RoboidControl {
Thing child = this.children[childIx]; Thing child = this.children[childIx];
if (child == null) if (child == null)
continue; continue;
child.Update(recurse); child.Update(currentTimeMs, recurse);
} }
} }
} }
/// <summary>
/// An event happened to this event
/// </summary>
/// <note>The messageId indicates which kind of event happened
public class CoreEvent { public class CoreEvent {
public CoreEvent(int messageId) { public CoreEvent(int messageId) {
this.messageId = messageId; this.messageId = messageId;
} }
/// <summary> public int messageId; // see the communication messages
/// The type of event happened.
/// </summary>
/// This value is filled with the Ids of the communication messages.
public int messageId;
}; };
/// <summary>
/// Queue containing events happened to this thing
/// </summary>
public ConcurrentQueue<CoreEvent> updateQueue = new(); public ConcurrentQueue<CoreEvent> updateQueue = new();
/// <summary>
/// Get the current time in milliseconds
/// </summary>
/// <returns>The current time in milliseconds</returns>
public static ulong GetTimeMs() {
#if UNITY_5_3_OR_NEWER
return (ulong)(UnityEngine.Time.time * 1000);
#else
return TimeManager.GetCurrentTimeMilliseconds();
#endif
}
#endregion Update #endregion Update
/// <summary> /// <summary>

View File

@ -52,14 +52,13 @@ namespace RoboidControl {
float pidD = 0.0F; float pidD = 0.0F;
//float pidI = 0.0F; //float pidI = 0.0F;
public override void Update(bool recurse = false) { public override void Update(ulong currentTimeMs, bool recurse = false) {
float actualSpeed = this.encoder.angularSpeed; float actualSpeed = this.encoder.angularSpeed;
// Simplified rotation direction, shouldbe improved // Simplified rotation direction, shouldbe improved
// This goes wrong when the target speed is inverted and the motor axcle is still turning in the old direction // This goes wrong when the target speed is inverted and the motor axcle is still turning in the old direction
if (this.targetAngularSpeed < 0) if (this.targetAngularSpeed < 0)
actualSpeed = -actualSpeed; actualSpeed = -actualSpeed;
ulong currentTimeMs = Thing.GetTimeMs();
float deltaTime = (currentTimeMs - this.lastUpdateTime) / 1000.0f; float deltaTime = (currentTimeMs - this.lastUpdateTime) / 1000.0f;
float error = this.targetAngularSpeed - actualSpeed; float error = this.targetAngularSpeed - actualSpeed;

View File

@ -86,7 +86,7 @@ namespace RoboidControl {
} }
/// @copydoc RoboidControl::Thing::Update(unsigned long) /// @copydoc RoboidControl::Thing::Update(unsigned long)
public override void Update(bool recursive = true) { public override void Update(ulong currentMs, bool recursive = true) {
if (this.linearVelocityUpdated) { if (this.linearVelocityUpdated) {
// this assumes forward velocity only.... // this assumes forward velocity only....
float linearVelocity = this.linearVelocity.distance; float linearVelocity = this.linearVelocity.distance;

View File

@ -52,14 +52,13 @@ namespace RoboidControl {
float pidD = 0.0F; float pidD = 0.0F;
//float pidI = 0.0F; //float pidI = 0.0F;
public override void Update(bool recurse = false) { public override void Update(ulong currentTimeMs, bool recurse = false) {
float actualSpeed = this.encoder.angularSpeed; float actualSpeed = this.encoder.angularSpeed;
// Simplified rotation direction, shouldbe improved // Simplified rotation direction, shouldbe improved
// This goes wrong when the target speed is inverted and the motor axcle is still turning in the old direction // This goes wrong when the target speed is inverted and the motor axcle is still turning in the old direction
if (this.targetAngularSpeed < 0) if (this.targetAngularSpeed < 0)
actualSpeed = -actualSpeed; actualSpeed = -actualSpeed;
ulong currentTimeMs = Thing.GetTimeMs();
float deltaTime = (currentTimeMs - this.lastUpdateTime) / 1000.0f; float deltaTime = (currentTimeMs - this.lastUpdateTime) / 1000.0f;
float error = this.targetAngularSpeed - actualSpeed; float error = this.targetAngularSpeed - actualSpeed;

View File

@ -18,7 +18,7 @@ namespace RoboidControl.test {
ulong milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); ulong milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
ulong startTime = milliseconds; ulong startTime = milliseconds;
while (milliseconds < startTime + 7000) { while (milliseconds < startTime + 7000) {
participant.Update(); participant.Update(milliseconds);
Thread.Sleep(100); Thread.Sleep(100);
milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
@ -34,7 +34,7 @@ namespace RoboidControl.test {
ulong milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); ulong milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
ulong startTime = milliseconds; ulong startTime = milliseconds;
while (milliseconds < startTime + 7000) { while (milliseconds < startTime + 7000) {
siteServer.Update(); siteServer.Update(milliseconds);
Thread.Sleep(100); Thread.Sleep(100);
milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
@ -51,8 +51,8 @@ namespace RoboidControl.test {
ulong milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); ulong milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
ulong startTime = milliseconds; ulong startTime = milliseconds;
while (milliseconds < startTime + 1000) { while (milliseconds < startTime + 1000) {
siteServer.Update(); siteServer.Update(milliseconds);
participant.Update(); participant.Update(milliseconds);
Thread.Sleep(100); Thread.Sleep(100);
milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
@ -73,8 +73,8 @@ namespace RoboidControl.test {
ulong milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); ulong milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
ulong startTime = milliseconds; ulong startTime = milliseconds;
while (milliseconds < startTime + 7000) { while (milliseconds < startTime + 7000) {
siteServer.Update(); siteServer.Update(milliseconds);
participant.Update(); participant.Update(milliseconds);
Thread.Sleep(100); Thread.Sleep(100);
milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); milliseconds = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();