Home http://jacobshepherd.knight.domains A Train Tracking Senior Project Thu, 11 May 2023 17:19:33 +0000 en hourly 1 https://wordpress.org/?v=6.2 http://jacobshepherd.knight.domains/wp-content/uploads/2023/02/First_train_photo-2-150x150.png Home http://jacobshepherd.knight.domains 32 32 Final Thoughts http://jacobshepherd.knight.domains/blog/final-thoughts/ http://jacobshepherd.knight.domains/blog/final-thoughts/#respond Tue, 09 May 2023 21:38:46 +0000 http://jacobshepherd.knight.domains/?p=159 Now that I have completed my capstone I felt like sharing my final thoughts. As a whole I found this project to be very rewarding. This project allowed me to work with concepts from almost every course I have taken during my time here at SNC. I worked with OS concepts, threads, events, game programming, UI, and data structures. Working with all those different concepts really made this project feel like a culmination of everything I have learned, it really felt like a capstone on my time here.

That being said, I was not very fond of the beginning of the course. It felt as though there was a lot of busy work that took away from time that I could have been working on my project. Had I not been caught up in readings and research I would have been able to implement more of the changes listed in my previous post. I understand that the readings were valuable but I wish they were learned at a different time. I would have loved to be able to meet the three times a week and always be sharing an update on my code or working with the class on a bug but most of the time that was not possible. I think it may be beneficial in the future to approach this capstone in the same way that philosophy and theology approach their thesis assignments; one semester long 2 credit course of research in fall and one semester long 2 credit course of working in the spring.

Although I have critiques I really enjoyed the overall experience especially because it gave me the opportunity to bond with many seniors I had not spent much time with prior to this course.

]]>
http://jacobshepherd.knight.domains/blog/final-thoughts/feed/ 0
What I Would Add http://jacobshepherd.knight.domains/blog/final-post-what-i-would-add/ http://jacobshepherd.knight.domains/blog/final-post-what-i-would-add/#respond Tue, 09 May 2023 20:43:54 +0000 http://jacobshepherd.knight.domains/?p=151 For this final post I would like to take the time to address some things that I would have added or changed if I had the time to continue working on this project.

First is a simple change. I would change the deadlocking algorithm so that instead of deleting the train who caused deadlock I would send that train in reverse out of the section. In order to do that all I would have to do is my normal check for deadlock function and if that returns true then I would check for deadlock in the section I just left if I enter in reverse and if there is no deadlock there then change the section I am in to reverse and the client target to the first waypoint in the section, we will have already passed it. In the case that there would be deadlock if I went in reverse I would have to either make the code robust enough to keep checking for trains it can reverse out or make the client train disconnect since it cannot reverse out right away.

A larger change is the way that I connect the new clients to the server. What I currently do is load the track scene and on the first update I start trying to get the data from the server to initialize the track. This is inideal because it means that if any of the data is corrupted or the server does not respond in time all I can do is display a failed to connect message and tell the player to quit the game. The better way to do this is to set up the client before loading the track scene. This would mean contacting the server, receiving initial track data, saving that data to a file, loading the track scene, and reading the data from the file. The change to a file allows me to sit on the main menu and wait for responses from the server rather than sit in game and do the same. If the server fails to respond or gives unusable data I can prompt the user to try again instead of telling them to quit and reload.

I would also like to add spectator mode. This would be relatively easy to implement. All I would need to do is allow clients to join but if the server has reached a max amount of trains I set that client so that the only messages they can send to the server are disconnect messages. I would also set the spectators turnouts to all be disabled and would not display nor activate the train control UI. I would also edit the client initialization to only create a client train if the user was not a spectator. These changes would mean that a person could simulate the game like everyone else and would recieve the same updates as everyone else but would not be able to alter the track in any way.

I would also like to add a synchronization feature. This could be done by adding a new message type called REACHED. When your client train has reached a waypoint it will send a REACHED message to the server which would include its train id and the waypoint id it has reached. The same message will then be relayed to all the other clients and if on their end the train with that id is not currently targeting that waypoint id or the waypoint immediately following it the train would be teleported there. This would mean that each client would be the source of truth for its train and the server would communicate those true locations to the rest of the clients. If in testing I find that these messages come to frequently for the server to handle or that these messages cause other issues related to their frequency I have also considered a less frequent approach using the ENTER message. Instead of using REACHED we could use the ENTER message to ensure that when a given client enters a section all other clients also have them about to enter that section or in that section. These messages would be less frequent and easier to handle but would be less accurate to the true locations of the trains.

Finally I would like to add a bit of code to the server which would handle if a client’s game crashes unexpectedly. I have a whiteboard sketch of a possible way to handle this but it involves threads and I did not have enough time to debug a whole new thread as many strange issues come along with them. The juist of the solution is as follows. Start a timer for N seconds. After N seconds send a message to all clients who have not sent us a message since the timer started. If those clients respond with an empty message skip over them. If they respond with a message that is not empty add that message to a queue and skip over them. If they do not respond at all mark them for disconnection. After looping over all the clients who need to be checked we disconnect the clients who are marked for disconnection. After disconnecting the dead clients we handle all the queued messages. Finally mark the timer as complete, mark all clients as not responded yet, and kill the thread. The main thread will then see the timer is not going and will restart the timer creating a new thread to repeat the process after N seconds.

]]>
http://jacobshepherd.knight.domains/blog/final-post-what-i-would-add/feed/ 0
Update: Presentation Done http://jacobshepherd.knight.domains/blog/update-presentation-done/ http://jacobshepherd.knight.domains/blog/update-presentation-done/#respond Tue, 02 May 2023 21:57:11 +0000 http://jacobshepherd.knight.domains/?p=140 This weekend I had the pleasure of showing off my capstone project during presentation day. I want to thank anyone who was able to attend and support myself and all of the other seniors presenting. I believe my presentation went very well (although I did run a bit long). Now all I have left is my defense and turning in all the final documents. This will likely be my last blog post. Thanks to anyone who was following along and sorry for the erratic posting schedule.

]]>
http://jacobshepherd.knight.domains/blog/update-presentation-done/feed/ 0
Update: Three Clients http://jacobshepherd.knight.domains/blog/update-three-clients/ http://jacobshepherd.knight.domains/blog/update-three-clients/#respond Thu, 27 Apr 2023 02:07:23 +0000 http://jacobshepherd.knight.domains/?p=90 Today I tested the program with three clients on the server at once. The program functioned fairly well with only one major issue. The bug was in the train data that was being sent from the server to the third client. When collecting the train data I accidentally wrote message = train.getData() instead of message += train.getData() so for 2 trains it works fine but for 3 or more only the newest train was being sent. With this fixed I was able to spawn the trains in the right spots and get things moving in the right way for the most part. There are occasional issues where one train misses a message from the server and so it is no longer simulating the same as the others but this should be easy to fix if I just send the message a few times. Given how low on time I am (Thursday presentations are tomorrow) I think I will not make the change just to prevent any catastrophic issues from occurring right before I have to prepare to present.

]]>
http://jacobshepherd.knight.domains/blog/update-three-clients/feed/ 0
Update: Deadlocking http://jacobshepherd.knight.domains/blog/update-deadlocking/ http://jacobshepherd.knight.domains/blog/update-deadlocking/#respond Thu, 27 Apr 2023 02:01:14 +0000 http://jacobshepherd.knight.domains/?p=85 As part of the requirements for the project TOS is meant to handle some OS concepts like deadlocking. With the deadline coming quickly I was not sure if I would be able to implement any deadlock related code. What I decided to do was try and come up with a minimal change solution. The plan was to leverage the existing code as much as possible so that if my plan did not work out it would be easy to comment out changes and get back to the working version. The design that I came up with was quite intuitive and did not take too much thinking, luckily on a train track its very easy to spot deadlock. Deadlock only occurs on a train track when there each possible exit of a given section is attempting to enter your section. For example in a section shaped like a U, where the endpoints of the section fall at the tips of the U and one in the middle, a train (A) traveling down the left arm would be headed towards a deadlock if another train (B) were heading down the right arm. If A were headed down the left but B was headed up the right instead of down then B would eventually exit the section and A would be able to proceed. The algorithm for checking for deadlock then is quite easy, when a train enters a new section it must check if all adjacent exit sections are occupied and are heading in the opposite direction to it. I decided that the train that enters last, the one causing the deadlock, should be destroyed to prevent deadlock and allow the other trains to proceed. Below is the whiteboard mock up of the algorithm.

In code the solution proved to be as minimal as I would of liked and was implemented about two hours after the initial algorithm was developed. When a train enters a section the track checks if its the client’s train. If no nothing happens, if deadlock were to be coming the other client would have dealt with it already. If it is our client train then the track checks where the train will be exiting. For each of the potential sections that the train will exit to the track checks if it is occupied, if not then no deadlock. If the exit section is occupied then we check which way it is currently oriented, if it is not towards the endpoint that we are heading towards then no deadlock, that section will clear. If any section reaches the no deadlock threshold then we return happily but if all of them meet deadlocking criteria we remove the client train.

]]>
http://jacobshepherd.knight.domains/blog/update-deadlocking/feed/ 0
Update: Two Client Problem IV http://jacobshepherd.knight.domains/blog/update-two-client-problem-iv/ http://jacobshepherd.knight.domains/blog/update-two-client-problem-iv/#respond Mon, 24 Apr 2023 16:23:43 +0000 http://jacobshepherd.knight.domains/?p=78 Working with Dr. McVey I was able to resolve several problems.

The first problem we tackled was the disconnection of a client because this was not working on the client or server side of the application. On the server side we found that when removing a client I had accidentally left the numClients–; call inside a loop so it was reducing the numClients to be negative and causing bounds errors. This fix was extremely helpful because it means that the server can be left online between tests and I can connect and disconnect freely instead of restarting it every test. kOn the client side the issue was more complex. Unity has the Destroy() function which takes in a GameObject and removes it from the scene. To remove the train of a disconnected client I was passing train.transform.parent.gameobject into Destroy(). Intuitively this would get the parent GameObject of the train and remove it from the scene. We would want this because when we destroy the train from the scene we do not only want to remove the train component we want to remove everything associated with that train everything attached to the parent object. However, what we found in testing is that every train on track shares a single parent GameObject so destroying that parent would destroy all the trains. To resolve this we simply moved one layer down and destroyed train.transform.gameobject.

The more valuable fix was the adjustments made to the spawning of trains. When we started trains were spawning in seemingly random positions at seemingly random times. The idea was that if Client A’s train is at position p1 moving to position p2 then on Client B’s side I should spawn Client A’s train at p2 to account for the time that passes between the location request and the spawning of the train. p2 is the difference between the current location of the train and the location of the waypoint that the train is targeting. p2 is changed every game tick. While testing we were able to find why this method was not working. the calculation of target – p1 = p2 does not do what I had assumed. Rather than placing a point somewhere between the target and the train for the train to head towards the calculation overshoots the target and slowly moves closer to the target, something like the photo below.

This photo helps to illustrate how the point to which the train is traveling is beyond the intended target but every game tick the train and p2 are updated. As we move closer and closer to the target p2 converges with the target and the train eventually reaches its destination.

Knowing that I could not use p2 to adjust for the time passing between messages sent from client to server to client, I decided to simplify and hope. Instead of sending where its going I send the exact location of the train and hope that the amount of time from send to recieve is not so long that the information is inaccurate. Thus far in testing I have found that the position is close enough to accurate that without extremely careful examination a person cannot tell the two simulations are different.

]]>
http://jacobshepherd.knight.domains/blog/update-two-client-problem-iv/feed/ 0
Update: Two Client Problem III http://jacobshepherd.knight.domains/blog/update-two-client-problem-iii/ http://jacobshepherd.knight.domains/blog/update-two-client-problem-iii/#respond Thu, 20 Apr 2023 06:52:57 +0000 http://jacobshepherd.knight.domains/?p=76 Before I start with the two client progress I must mention that I added the ability to provide a username at the start menu and made that username follow the train around so that we can see whos who on track (eventually).

Today I made a major breakthrough that should have been a minor one. Allow me to explain. During the flow of a game there are only 3 messages that a client knows it’s going to recieve, all three come from the server directly telling the client how to initialize the track. Because we know these are coming we can sit and wait for them. During client setup my code waits a set amount of time for each of these messages and if the amount of time is used up or a message is corrupt, they disconnect. This wait for the message method only works when we know a message should be on its way because waiting blocks all other code from executing. The way around this is to run an asyncListen() instead of the regular listen function. Asynchronous listening creates a second thread which blocks until a message is received. This means that I can listen all the time in the background while simulating the game at the same time. This function receives all of the other messages like when another train joins, a turnout is clicked, a train changes speed, ect. asyncListen() terminates just like a regular listen when it receives a message. In order to always be listening I simply had a bool listening and a function StartListening(). Every tick I check the listening variable and if the listening variable is set to false I call start listen which starts the asyncListen() and sets listening to true. What I forgot to do was set listening to false when the asyncListen() returned. So what was happening was the variable started as false then a listen started and it turned to true then never turned back to false so only the first message was received. After fixing this I now have some things working from client to server to client. For example, I can change a turnout on one and it changes on the other but a disconnect does not remove the train from the track for other clients.

]]>
http://jacobshepherd.knight.domains/blog/update-two-client-problem-iii/feed/ 0
Update: Two Client Problem II http://jacobshepherd.knight.domains/blog/update-two-client-problem-ii/ http://jacobshepherd.knight.domains/blog/update-two-client-problem-ii/#respond Thu, 20 Apr 2023 05:27:02 +0000 http://jacobshepherd.knight.domains/?p=74 Further development in the two client problem. Today when attempting to recreate the extreme failure of player B I was met with different problems but not the exploding. I tinkered with many pieces of code but still do not know what I changed that fixed the freezing all I really recall doing was adding several Debug.Log calls to see exactly when the program was failing. After getting the second train to not explode I was able to learn that there were several other things wrong with both the server and client side of my programs.

On the client side I have the strange error that although Player B finishes the SpawnTrain() function which spawns in the Player A train, the actual train entity does not appear for several seconds and appears in the wrong location. I have no idea why the train waits to spawn even though the function completes. However, I do suspect that the issue of where the train is spawned is due to the funky math I do to spawn it. When Player B joins the game they notify the server. The server then sends all the available track data and asks player A where their train is. Player A then sends its location relative to the location of its current target waypoint along with the target waypoint index, section its in, and speed its traveling at. The relative location is found by taking the difference between the trains xy and the targets xy. Knowing this I assumed I could simply reverse the calculation, take the targets xy and add the difference xy and you get the trains location. In theory it should work but I believe I am mistaken in some assumption along the way. Another issue is that none of the messages that should be sent from one client to another seem to be getting there (except for the location request).

On the server while inspecting the missing messages issue I found that for some reason messages were being received and then sent to clients seemingly randomly. Sometimes messages were sent from A to A and B, sometimes from A to B or A to A, and sometimes from client A to no one. This bug was easy to track down and fix. To send my messages I use a function called SendToAllExcept which took an IpEndPoint object (a prewritten class from the .net.sockets library which represents IP Address Port pairs) and a message. The function would loop through all the clients connected to the server and if they were not the endpoint provided to the function the message would be sent to them. When analizing this function for issues I realized that it was overly complex. I did not need to exclude based on an endpoint because when receiving any message the server checks if it came from an existing client or not and stores which client sent it if they exist. This means that all I needed to compare in that function is the index of the client not the addresses. This is the better way to do it but when implementing this way I realized why the original implementation did not work. I was using: if (endpoint_a.Equals(endpoint_b)) instead of if( IpEndPoint.Equals(endpoint_a, endpoint_b). I believe the subtle difference meant that instead of using the .Equals to compare IpEndPoints I was using a .Equals which compared objects. After changing to the index send I was able to send the right messages to the right places but the messages still do not seem to be received or handled appropriately by the clients.

]]>
http://jacobshepherd.knight.domains/blog/update-two-client-problem-ii/feed/ 0
Update: Two Client Problem http://jacobshepherd.knight.domains/blog/update-two-client-problem/ http://jacobshepherd.knight.domains/blog/update-two-client-problem/#respond Thu, 20 Apr 2023 04:58:42 +0000 http://jacobshepherd.knight.domains/?p=72 Today I was able to use my roomates laptop to attempt to run my game on two remote clients at the same time. It did not go very well. Currently the problem goes like this: Player A connect. Player A game runs smoothly. Player B connect. Player B game stops running and windows throws the popup “Capstone has stopped responding” and prompts player B to close. Player A remains blissfully unaware of player B exploding and runs as if player B never even existed I suspect this is because player A was never informed of player B.

]]>
http://jacobshepherd.knight.domains/blog/update-two-client-problem/feed/ 0
Update: Remote Server http://jacobshepherd.knight.domains/blog/update-remote-server/ http://jacobshepherd.knight.domains/blog/update-remote-server/#respond Thu, 20 Apr 2023 04:53:13 +0000 http://jacobshepherd.knight.domains/?p=69 After a bit of research and some minor code changes my server program is now capable of running on a remote server (compsci04) and my client is able to connect to that remote point. So far it is only one client connecting. The biggest challenge with going remote was that the server runs on linux and I have never attempted to run an application on linux before. Luckily microsoft new was kind enough to make sure that any .NET application (most apps made in Visual Studio) can be run on any OS. To do this all I had to do was go to build in Visual Studio and click publish. In the publish menu I chose to send to a local file and then I copied the generated publish folder to compsci04. Once you have the publish folder on compsci04 cd your way into the publish folder and run the command: dotnet YourProjectName.dll. You might get an error saying that the .NET versions do not line up but telling the server to roll your code forward should fix this. That command is: dotnet –roll-forward Major YourProgram.dll.

]]>
http://jacobshepherd.knight.domains/blog/update-remote-server/feed/ 0