Although Scawen has fixed this bug in the latest LFS patch, I'm not going to change it in InSim.NET for the moment, as that would break backwards compatibly with old LFS versions (which still have the bug). For the time being the maximum length of the BTN text field will remain 239 bytes.
If you can show me an example of the struct, then I will compile a special version of InSim.NET for you with support for those games. I would need to know exactly what data the packet is to contain however, as without that I can't do anything. If you provide the specification, it's no problem to create a new build.
I'm not sure I'll add this to the main trunk, I may create a separate fork for it. It depends what data the struct contains really. You could create a fork for it yourself, if you're so inclined.
Well, as I said in my post, leaking memory here might be an issue. You do need to 'unhook' any events which are attached to the timer before it goes out of scope. Dangling event-handlers are the number one cause of .NET memory leaks. This will be even more of an issue if your program is designed to run for weeks at a time, like a LFS server app.
Really the best practice is to try and reuse your timer objects wherever possible, however if that is not possible then you should unhook the event-handler once you are done with it, and before that object goes out of scope.
timer.Elapsed -= timer_Elapsed
This is one of the most common memory leaks in the .NET, if not the most common, so you are correct to raise this as an issue.
I got the HD 4670 plumbed in today, working great. Was looking at the retail price for them and you can pick one up for about £40. Seems a pretty reasonable card for that price, although I'm obviously not an expert.
Basically the BTN Text field does not need a trailing zero unless the TEXT_SIZE is exactly 240 characters. To clarify, if the TEXT_SIZE is a multiple of four but less than 240, say eight or 128, then no null terminator is needed. However if the TEXT_SIZE is exactly 240 characters and there is no trailing zero, then LFS fails to display the button and there is no error message or warning.
Just to note that the new MTC packet always needs a trailing zero, no matter what the TEXT_SIZE is (and warns you if you omit it), so it would seem there is a bug/inconsistency here.
If you send a button that's a multiple of four then it gets displayed, despite lacking a trailing zero. However if the text is exactly 240 characters then it does not get displayed, unless you replace the last char with a '\0'.
Here is a Python script I used to test the issue. I send three buttons, first eight chars long (no trailing zero), second 240 chars long (no trailing zero), third 239 + trailing zero. Only the first and third buttons get displayed. There is no error message in LFS about the button, it seems it's just discarded.
The BTN text sometimes does need a null terminator. If you send a BTN with text exactly 240 characters long (no trailing '\0') then LFS just discards the button with no error message. This was a bug I had to track down recently in InSim.NET.
Edit: It occurs to me now that this may actually be a bug in LFS.
The MSO packet works correctly, you need to use the TextStart property to get where the text starts. This is not InSim.NET, this is how the packet is sent by LFS.
What's the name of the other player in the crash? I need to know both of them.
Um, no, the library is already multithreaded. If an exception gets thrown on a background thread you have no way of knowing. It will unwind that threads callstack and stops execution, but as each thread has its own callstack this won't affect your main program thread. Because of this I added the InSim.InSimError event that allows you to catch exceptions on background threads. You should pretty much always catch and log any errors raised by InSimError.
What I need to know is the name of the two players. The error occurs in part of the code that converts a string into bytes, so I need to know what the string actually was to figure out what happened.
That's probably because the error is being thrown on the background thread, as I've said before you need to hook up an event to InSim.InSimError to catch those. Once an error is thrown InSim.NET can't really continue, can it, it doesn't know what state the program is in any more, so it disconnects.
OK - I've pushed some fixes onto CodePlex, you can pull the latest version from the repository.
The bug was caused because the size of the BTN packet was being determined before the string had been converted into bytes, which meant that any string where the number of bytes > number of characters would cause it to crash. Anyway, I've fixed it, I hope.
I also fixed the new MTC packet to make it variable sized, plus made a couple of tweaks to IS_CON.
The timer gets disposed when the timer object is removed from scope, just like all .NET objects (more or less). The only thing you need to watch out for is that there are no stray event-handlers or references lurking in the background. These can cause memory leaks and other issues, but this is the same for all .NET objects, not just timers.
There are at least five different timers in .NET (from the top of my head) and each one works slightly differently. The only way to know how to use each one is to read the documentation (select the object and press F1 in VS2010).
Really the only important thing you need to know is that if there is a possibility that two threads may try to access a resource at the same time, then you will need to synchronise the access with locks. If you don't your app will work 99.9% of the time, but that last 0.1% will make you go insane.
Well, love or hate multithreading (um, I think everyone hates it), InSim.NET is a multithreaded library, and each timer you add spins up yet another thread. A console application using InSim.NET with a timer will result in three threads running at once, the main program thread, the InSim.NET* receive thread, plus also the timer thread.
Now if you try to update a global variable from a timer while the receive thread is trying to update it, you will run into serious and strange bugs (like this one). The solution is that you need to employ locking, which is pretty-standard for multithreaded apps, and something .NET makes pretty simple, to its credit.
You need to create some sort of object that you can lock on.
object lockObject = new object()
Whenever you manipulate QueueHolder (or any other variable which can be access from multiple threads), you call lock on that object to prevent anyone else from trying to update it at the same time. For instance.
lock (lockObject) { QueueHolder.Add(object); }
If another thread tires to access the QueueHolder it will first run into the lock, which will cause that thread to block until the lock is released. This makes it sure that only a single thread can access that resource at once, however it has the downside that every other thread will need to wait around for the first thread to finish. You need to do this at every point you update or access QueueHolder though.
The code you posted in that screenshot would become something like this (simplified and without testing)
public static void QueueProcessor_Elapsed(object source, ElapsedEventArgs e) { int[] UCIDTimes = new int[256];
lock (lockObject) { int Count = QueueHolder.Count;
for (int i = 0; i < Count; i++) { if (QueueHolder.Count > 0 && QueueHolder[0] != null && QueueHolder[0].Button != null) { int UCID = QueueHolder[0].Button.UCID; int idx = Dizplay.getUserIdxByUCID(UCID);
if (UCIDTimes[UCID] < maxPerUCID) { clsUser U = Dizplay.Users[idx];
In this instance we just wrap the whole thing in a lock, which should do for these purposes, although really you want to make your locks as small and 'finely-grained' as possible. As I say, that's simplified and without testing through, but it should give you a good idea how to progress.
For a really nice primer on threading in C# have a read of this free e-book (chapter two on synchronisation should be of special interest to InSim.NET users).
Threading is one of those things that seems so simple on the surface, until you run into your first concurrency bug and realise that it's a black pit of death you can never escape from. Oh well...
* This is the same for every InSim library ever released, it's not peculiar to InSim.NET. It shows how weird and strange these bugs can be as to how seldom this has come up. You can guarantee that they will come up eventually though, given enough time. Incidentally this is why Windows doesn't let you update controls from different threads (and why you need to marshall invokes across the dispatcher), by allowing cross-thread access they'd open themselves up to thousandths of strange bugs they'd never be able to figure out.
Edit: Another option would be to make InSim.NET appear to run single-threaded by using a SynchronizationContext, which is possible and I might write an example of how to do that later. It would make the library much less efficient though, which is why it doesn't do that by default.