Thanks guys, I think I get it now. I just have to find the One Java Way now. ;-)
I did looked into the InSimDotNet.2.0.14b sources, though I'm quite innocent in .NET , I think I could make two conclusions from what I've seen.
1st is that I have to prepare for
- no bytes
- some bytes of a packet
- a packetful of bytes
- one (or more) packetful of bytes
- one (or more) packetful of bytes and then some bytes
The current implementation doesn't seem to support more bytes at one read, neither a packet and then some. (That is if I'm reading it right.)
The second was the callback mechanism. The solution that is currently applied in JInSim's AbstractChannel class is based on a continously running loop doing 10 ms sleeps, and polling the SocketChannel in every loop for new bytes.
I can already see, that the callback mechanism is objectively better, because
- the program can react instanteneously when new bytes arrive (otherwise, in worst case, I can loose up to 10 ms right there)
- it can avoid unnecessary loops, so less processor cycles are needed.
I already found that there is a mechanism for this provided by the Java SocketChannel API, it's my turn to learn, how to utilize it.
I'm thinking, what if the buffer receives more than one packet's bytes within the same cycle of the forever running loop. buffer.position() will never be equal to size then...
If that can happen, the next packet size reading would be in trouble (either missing whole packet or packets, or worse, reading an arbitrary byte from the middle of a packet)...
In InSim.txt I see these Enum members, but I can't figure out, what are they for (they don't have comment after them, they are always the last enum):
MSO_NUM
SND_NUM
VOTE_NUM
PITLANE_NUM
PSE_NUM
LEAVR_NUM
PENALTY_NUM
PENR_NUM
TYRE_NUM
PMO_NUM
DL_NUM
I have implemented the Visitor pattern I mentioned, and it worked out great. It took me some time however, to come up with just the right class/interface names. First I called my Visitors "Processor", and the Visitable "Processable", but then I realized that a better name would be "Receiver" and "Receivable".
Especially because I could mark other packets as Sendable (and of course I could have packets which are both Sendable and Receivable.
I turned my attention to the packet factory mechanism, which is a really nice implementation of the factory pattern, but I've found some things I don't like. E.g. the fact that it has to iterate through the packet types twice: once for finding out the packet type, then to check if it is "registered". ("Registered" meaning it is receivable.)
The other thing I don't like is that there are "Requests" and "Responses", but the InSim protocol is a little bit more distinguished than that: there are "info request", "info", "both ways" (meaning both info request and info), and "instruction" (which are requests that don't get responded).
Currently I have these group names in mind:
- "inbound": for the "info" packets; these can be marked with "Receivable" interface
- "outbound": for the "info request" and "instruction" packets; these can be marked with "Sendable" interface
- "duplex": for the "both ways" packets (I don't want to implement a "both way" twice: once as a request and once as a response); these can be marked with both the "Receivable" and the "Sendable" interfaces.
I'm in the process of evaluating these ideas. Hopefully, I'll have some results to show for it soon.
"I am using my grandad's pants"
It's pretty funny, though. Even if I take into consideration the fact that it wasn't me who said it (aand I dunno who was). :P
You better have a good amount of humour if you wanna enjoy the beauties of the WORLD WIDE -web- LFS.
I was thinking about how could I get rid of the instanceof checks in JInSimListeners. I've been reading about pros and cons of using instanceof checks, and I've found that it's fine if I want to switch between a couple object types, but it doesn't scale well, if the number of types is bigger.
For example, if I have ten different types of packets coming in, in case of the tenth, nine extra instanceof condition-check is needed, for every single packet.
To make it worse, if I want to have more, and specialized Listeners ("high cohesion"), I have to give every packet to every Listener, so a packet will be checked for type many times. *OR* I could create one big central control Listener to dispatch all packets between specialized Listeners, but that would need as many instanceof checks as many types we have (it would be really bad for the ones that happened to get at the end of the condition-check list.
Looking for an alternative solution, I've found that *Visitor pattern* is generally recommended for this situation.
The big pro for Visitor is the fact that I, as the user of JInSim library, can make one (or any number, as needed) special Visitor (or as I like to call it: "Processor") class for every packet type I need to handle.
At runtime, there's no need for any instanceof checks, every packet is directly dispatched to the proper Visitor object by Java's polymorphic method selection mechanism between superclass and subclass Visitors.
The con for Visitor is that I have to modify the code of each packet class, and create the Visitor interface with as many visit() methods, as many types we have. That would be fine in itself, but if I want my modified JInSim library to support a new type of packet later, I would have to modify this interface too. (And the abstract adapter class as well, which would be positioned between the Visitor interface and the Visitor implementations, so that implementations need to implement only one "visit" method.)
This is my current understanding of the factors of this problem. I hope I can come up with a proof of concept version soon.
This changes the picture of the current Observer pattern: a "give-me every type and let me typecheck" Listener could co-exist with a "call-me when I'm needed" Visitor, but it would be easier to just write Visitor classes, and subscribe *them* to the Client.
I'm thinking of this problem while keeping in mind that packets can also be separated between handling classes by their Request ID-s.
So essentially, a request ID aware client could dispatch packets by their ID-s, and their types at the same time, without extra typechecks at RUNTIME!
I'm just scratching InSim programming, so help me to understand this conceptionally, please. This extension is useful for *client-side* InSim programs, isn't it? Or can it be useful for example to construct a tracker's UI too?
PS. Just a friendly heads up for another non native English speaker: "Object hitted" is grammatically incorrect, because the past tense and past participle of the verb "hit" is "hit". (You know: hit, hit, hit.)
How can "less information" be better for an export function than "more information"?
I can see another potential problem with stripping colors (this without trying out anything): http://en.lfsmanual.net/wiki/FAQ says "^9 - Original text colour and codepage". If ^9 is stripped too (or is it not?), how can we be sure what codepage should be used?
Well, this conversation turned out to be somewhat single-sided, but I hope that it will help someone someday.
I've changed my latest method (I believe this will be the last change) so that
- at one wheel button press the feedback button (which rather serves as a label) appears and stays permanently on screen, until either
- the same wheel button is pressed which executes my customized admin command
- another wheel button is pressed, which revokes the action.
This makes it possible to wait for the very last moment (e.g. with a !spec command) not worrying about the timer. And if it's not necessary, just dismiss the warning without taking any action.
Since all three approaches I devised so far leave something to be desired, I think I will go with a fourth one, which goes like this.
1. User presses wheel button (assigned to a "/o mycommand" text command, also with an Alt+ or Ctrl+ keyboard combination)
2. A non-clickable button comes up with the feedback. This serves as a confirmation and sports a countdown back from (let's say) five to zero by the second.
3. If user presses the wheel button again before this button disappears, the confirmed command will go through, otherwise nothing happens.
My next feature-request is an abstraction of creating "compound" buttons i.e. buttons which are grouped together by a non-clickable background button.
It's basically similar to html tables: rows and columns, with rowspan and columnspan. Hey, wouldn't it be dreamy if I could define my LFS user interface by simply editing a html table (xml code), and it would be generated for me?
Sweet Jesus, dear admin, you deleted the *other* one instead of this. (Or was it merged, so the housekeeping conversation appears here?) Oh well, me and my adventures...
Okay, on topic then.
I've discovered a couple of potential approaches to this problem. Still not perfect, but getting there.
The first one was, since I didn't really need the pre-typed in message to be editable, I just wanted a "confirm" button (the message is supposed to be an admin command with the actual player's name). So I created a "compound" button, just like the message entry dialog, without the text field: nice big "Cancel" and "OK" buttons side by side, over a common background button. A picture serves better:
My only problem with this is that I have to use the mouse for pressing the buttons. I'd rather like if Enter worked for OK, Esc for Cancel, just like for the text entry Dialog.
How can I do that?
In the meantime, I've discovered, that I can send a "/press T" message, which results in the message entry dialog box popping up. I still have to figure out though, how to put my message into the textfield.
I've tried sending consecutive "/press a" "/press b" etc. commands, but the letters didn't end up in the textfield.
Sending "/ctrl v" after putting my message to system clipboard does fill the textfield, so it's almost perfect. Almost, because escape codes are shown instead of being interpreted.
I'm not sure if LFS clipboard allows escape coded text to be copied in-out...
Some handy tips for button users (maybe this could go into net.sf.jinsim.examples.button.Main):
When you want to create text entry dialogs (typeIn buttons), and initialize them with some text at the same time, you can do this (in Java 7 you can use binary literals, very handy when dealing with bit patterns):
//Set the texts //this will be on the button, and also in the text entry dialog as initialized text String typeInText = "typeInText"; //the title of the text entry dialog String buttonTitleText = "titleText"; buttonRequest.setText("\u0000"+buttonTitleText+"\u0000"+typeInText);
//Enable typeIn button //This would enable typeIn type button, but nothing appears in the text entry box //because the character number has to be larger than the length of typeInText //buttonRequest.setTypeIn( (byte) 0b1000_0000 ); //0 character
//This is the maximum value buttonRequest.setTypeIn( (byte) 0b1101_1111 ); //95 character
And for the sake of nice readable code, I can do it like this:
Edit:
Ouch, am I green or what. I intended to post this in the general programmer forum. Forum admin, could you please delete it from here? Thanks and sorry for the noise.
Edit2: Okay, this thread is the one to stay. Here we go.
****
Is it possible to open the text entry dialog directly (without clicking on a button first)?
If not, is there a way to activate ("click on") a button by an Alt+ or Ctrl+ key combination/shortcut? In that case I could create an "invisible" button, and assign the shortcut to that.
What I'm trying to accomplish is this: by pressing a keyboard shortcut (or a wheel button) a text entry dialog should come up with some text already typed in.
I have some ideas so I write them here mainly for the record, but comments or guidance are welcome.
Shouldn't the client fire some event when its channel goes down?
Edit: When I was using UDPChannel, and I closed LFS, my connected client's isConnected still reported "true". No such problem with TCPChannel.
Maybe regular (re)connection attempts could also be part of the library.
Edit:
Also, keeping an up-to-date list of currently connected people with on-track off-track status indication seems like a common requirement. (And not a trivial one at that, AFAICT.)
Edit:
Message/names encoding-decoding (codepages, color codes, escaped characters), maybe it's in there, I just haven't stumbled upon it yet.
Edit:
I've not only found the Encoding class, but already caught a bug (missing feature) in it. There's this line in encodeString(String text) method:
<?php if (ch > 0 && ch < 128) { ?>
This exludes the zero character '\0' a.k.a. '\u0000', which is a problem, because zero characters are needed for initializing text in button text entry dialogues. Excerpt from insim.txt:
// On clicking the button, a text entry dialog will be opened, allowing the specified number of // characters to be typed in. The caption on the text entry dialog is optionally customisable using // Text in the IS_BTN packet. If the first character of IS_BTN's Text field is zero, LFS will read // the caption up to the second zero. The visible button text then follows that second zero.
// Text : 65-66-67-0 would display button text "ABC" and no caption
// Text : 0-65-66-67-0-68-69-70-71-0-0-0 would display button text "DEFG" and caption "ABC"
I'm inclined to change that line to this:
<?php if (ch < 128) { ?>
This includes 0 (and no need to check the lower boundary, since chars' minimum value is zero). I wonder what was the reason behind excluding it in the first place...
Why do TinyRequest and SmallRequest default to RequestInfo (reqI) 254?
Edit: oh, I can see it now. LFS reports "InSim : TINY_PING with no ReqI - QuickSpec" if it was sent with reqI = 0. Maybe it's there somewhere in the documentation?
Edit: ah, there it is in InSim.txt:
// ReqI : non-zero (returned in the reply) // SubT : TINY_PING (request a TINY_REPLY)
Now I just need to find out, why is it 254 (why not 255, e.g.) ?