Skinning on its way.

Admin

So there I was, sunk into a fug of getting nowhere for months.

Then I finally said "Sod it. I have been MONTHS trying to get this damn thing working under Java 1.1. Exactly how many people in the last three months have looked at our site in a browser that might have 1.1 installed?"

It turned out that there were six. Most of whom were either ourselves, or bots pretending to be old browsers. In total, Java 1.1 users came to about 0.1% of our userbase. Four years ago when we started working on this, Java 1.1 users were still a significant presence. Now, they are not. I have spent nearly a fifth of that time completely failing to get rich text working on 1.1.

It is sometimes very hard to let go, and rewrite. The Rich Text code I had written would generally compile and run without throwing any errors, it would just display wrong. This made it feel like getting it to display correctly was just a few tweaks away. And months and months of tweaks later, the code was a tangled mess. Holding an understanding of the whole thing in my head was impossible, so I would fix the part I understood, and find it had ended up breaking code in another part, the workings of which I had not been able to hold in my head at the same time. So I would fix the knock-on effects, and...

Reluctantly, then, I decided to move to try using Swing. This is available as a plugin for Java 1.1, and is build into Java 1.2 and later.

The next day, Rich Text now completely implemented, I went to work on skinning I looked into loading the images and audio files from zipfies, but while I could find a way to do it for the images, I couldn't for the audio files, so I think I will have each file as a separate thing - which will be easier for people writing other clients anyway.

I split loading of images and sounds off into separate threads, and all references to them are now concealed behind the skin, and an access class called ImageRef, which is cool because it means a placeholder image can be provided while the real image loads, and silently replaced - kinda like browsers do.

I suspect that I will need some kind of notification system to let them know that they need to redraw themselves once an image has loaded, though. I will need a similar system if I want text variables to update properly,

My first plan was to ignore images completely, and just use plain html for everything, with skinners using img tags if they wanted images, but that gave me less control over things like resizing, so I decided that the background image, the two board images, and the piece images should remain as images.

The comments below are from the top of the skin class file, and will give some idea of where I'm at with the skinning, now.

I intend to release the next version once I have this properly debugged, and before getting variables, internationalisation, styling, label/button html, and image notifications working properly. Those five things will come out in the release after next. Both should happen this month.

[code]/*
This class manages and wraps the resources for all skins, both
in chat and game. Each instance represents a different skin.

Statics ensure shared resources between skins are only loaded once.

I'd prefer more abstraction and less thud-centrism here.

USAGE:
Paths are overridden by setPath(ID, URL).
This sets a relative URL for one of the image or sound skin files.
If the URL is "default" then no image will be loaded and the placeholder
graphic will be used permanently, without an attempt to download.
If ID isn't a recognised image or sound file, the URL is silently ignored.

AudioClips are hidden behind playSound(ID) (wrapper around SoundList's play())
This call blocks until the clip has finished playing.
If ID isn't a recognised sound file, or is not yet loaded, the play request
is "silently" ignored (ahem).

To start files downloading, or to apply path changes, run init().
This doesn't supersede downloads started by other calls to init(), so be sure
to set all paths you need to override BEFORE calling it.
Downloads are done in other threads, so init does not block.

ImageRefs are gained through getImageRef(ID).
Images must ALWAYS be stored as their ImageRef, and used as myImageRef.im,
NOT storing myImageRef.im directly.
If the image is not loaded yet, a default image will be provided in the
ImageRef, and will be silently replaced once loaded. I suspect that this
will not be good enough, and I should write some imageLoadListeners.
Returns null if a default could not be given, eg if ID is unknown.

Strings are accessed by setString(ID, Str)/getString(ID).
Setting WINDOW sets the titlebar text if possible.
Setting a textfield as a string doesn't create a label, it puts an initial
value in the textfield.
Setting a new, unknown ID creates a label, with default constraints.
Getting a new, unknown ID returns the error string "No cow".

Dimensions are accessed by setMin/PrefSize(ID, x, y)/getMIN/PrefSize(ID).
Should rarely be needed as the defaults are reasonable.
MinSize prevents it shrinking past a certain point.
PrefSize prevents it growing past a certain point.
If FILL is set to BOTH/HORIZONTAL/VERTICAL, then PrefSize is ignored and the
component grows indefinitely in the given direction.
If setting a new ID, it remembers the dimensions but takes no further action.
If getting a new ID, or one for which no dimensions have been set, it
returns null, and the default dimensions must be used for that component.

Constraints are accessed by setConstraint(ID, key, val)/getConstraint(ID).
Setting one with a new ID creates a label, with text equal to its ID.
Getting one with a new ID returns null.
All constraints and settings are case-insensitive.
Constraints for strings, files, and WINDOW are pointless, but allowed.

An Enumeration of all the label keys can be gained by getLabelKeys().
It's a copy of the label list, so is thread-safe: if another thread adds a
label, the enumeration won't become wrong.
If no labels have been created, the Enumeration will be empty.

The config file has the following command types.

file
- equivalent to setPath().
constraint =
- calls setConstraint() multiple times. No spaces around the "="!
string
- equivalent to setString().
minsize
- equivalent to setMinSize().
prefsize
- equivalent to setPrefSize().

IDs come from globals. If a component or string id is unrecognised, a label is
made of that ID. Any number of labels may be created. Labels are
Multiple lines can refer to the same id: if attributes clash, the last line wins.
Attributes/values come from GridBagConstraints.

The globals below are marked which of the above they can be used with:
a = audiofile
i = imagefile
c = component
s = string

VARIABLES:
All (well, currently only MOST) strings can use most styled HTML (but not
javascript) and variables, in the case-insensitive form &Variable_Name;

The following variables are reserved, but not yet coded:
&teaminitial; = "t" or "d"
&myname;
&opponentname;
&trollname;
&dwarfname;
&trollscore; = not necessarily a number, may be "12 (20)" or somesuch.
&dwarfscore;

The downside is, the strings will probably NOT get updated immediately (or even
at all) when these variables change. Working on that.

INTERNATIONALISATION:
Browsers are annoying in that they munge locale, location and language into a
single setting: language. But at least that's more than you gt in ThudGame.
You can put an language indicator at the end of your string IDs if you want,
such as widdershins.nl, widdershins.de etc.
However, it will currently be ignored as an indicator, and considered as a
request for a new label. Sorry. I do hope to get i18n in later, after fixing
the variables.

FONTS, STYLES, ETC:
Yeah, I'll need some way of specifying those, too, won't I?
*/[/code]
--Yet another geek.

Click to Give Kudos
Development Notes
Login or register to tag items

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Re: Skinning on its way.

Admin

Gbleh. So, before I can release it, the last thing I need to do is to get the board itself (the thing with the pieces on) resizing according to the skin graphic sizes, the sizes specified by the skinner, and dynamically according to how much space there is in the window.

Sounds easy enough, right? Right.

Except, there's a LOT of code that relies on the specific size of the specific skin I used. This is an example:

  public int getBoardSquareX(int mouseX, int mouseY) {
    int result;
    if (viewingDiagonally) {
      // for /...    Offset is board dependent.
      squareWidth = boardWidth / 10;
      squareHeight = boardHeight / 10;
      result = (int)(((mouseY - BOARD_SKIN_VPAD)
       - mouseX*(squareHeight/squareWidth))
       / (-squareHeight) + 8.5);
    }
    else {
      squareWidth = boardWidth / 15;
      // +1 as int rounds down.
      result = (int)(mouseX/squareWidth) + 1;
    }
    return result;
  }

Doesn't look too scary, until you look at BOARD_SKIN_VPAD, which is some padding I added to the top so that the dwarfs heads would draw - in the skinnable version, you can add padding to ANY edge, so that's four times more work. And that "8.5" (it's "-1.5" in getBoardSquareY) is completely unexplained, and arbitrary - I know it was experimentally obtained. And there are 20 functions like this throughout the Board object, with these unexplained equations with their unexplained terms in... what was I thinking? Well, most likely I was thinking "I have one night to write this in ready for the demo, so I can cut a few corners"... and now it's come to bite me in the bum.

Frankly, I feel there must be an easier, more portable way to convert between screen coordinates, board coordinates, and server coordinates (like board coordinates, but sometimes rotated from how they appear on the screen).

Maybe make each square a lightweight component, so I know where the mouse is at any time, if the event handlers don't get confused and trigger in the wrong order - which they probably would and I'd still need to track where put PUT the components. In later versions of Java, you could use imagemaps. That would be interesting, but not an option for me now.


Click to Give Kudos
--

Yet another geek.


Re: Skinning on its way.

Admin

So it looks like I did comment some of it.

Here's one that just made me chuckle, though I suspect in the cold light of day it'll be less amusing..

// Viewing diagonally, rotated 45deg antiCW from
// 1,1 at topleft, so 1,1 at left now.
// Don't need to take octagonalness into account
// as we're measuring relative to board centre.
// halfway down the screen - distance from board
// centre * halfsquares + padding
result = (int)(BOARD_SKIN_HEIGHT/2
 - (squareX - squareY) * Sh/2 + BOARD_SKIN_VPAD);

Click to Give Kudos
--

Yet another geek.


Re: Skinning on its way.

this is all just a bit too hectic for me,but i can see where all your time is going,so thanks a lot.


Click to Give Kudos
--

_O_
ll( )ll
_] [_


Re: Skinning on its way.

Admin

It edges closer Smile

Over the last three days I procrastinated for a day (went to watch a movie, did mother's day shopping), programmed for a day, and farted about with a Java Profiler for a day.

In the process, I fixed the following glitches and show-stoppers:
x Diagonal drawing of the guidelines is mucked up.
x Vertical centring is ignored when drawing diagonally.
x Board sizes are ignored
X Components aren't transparent
X Sounds do not play on board
X Out of memory.

[Incidentally, in the following, KiB and MiB are megabytes and kilobytes respectively: it's the newer, non-ambiguous notation]

That last one was why I downloaded and installed a profiler. Turns out that although Sun claims it's the bee's knees when it comes to threading, Java actually can't cope with much of it. See, even if it doesn't use all of it, each thread needs an allocation of 528KiB (400 for Java code, 128 for native code).

I was loading each image and each sound in a separate thread, since that's the sensible way to do it, if you want to update the display once any of the images finishes loading. You can't, otherwise, wait until just one of the images loads: you have to wait until they're all done. Well, you can (and I do) but it involved jumping through hoops, and it's not at all elegant. But, if each image has its own thread, it prettymuch all handles itself, very elegantly.

But, there're 10 sounds and 27 graphics in the default skin. That meant that just to download them (since they all started downloading at the same time) it needed about 37*528KB, or about 19.5MiB. There are also about a further seven threads (it will vary depending on the JVM). Some are created by me, some by the JVM. So just in thread stack allocation, we're using 23MiB.

The default JVM size is only 16Mb, which is way too small. (all these sizes are probably bigger in the later versions, but that just means the problem is scaled up).

So, instead I had to write a version that loaded all the images through a single thread, and not use a thread for the sounds at all.

At just 8 threads, we're only using about 4MiB, which is saner. Running it in the profiler, though, I got it to allocate up to 100MiB at a time, between garbage collections. I don't know if this is going to be a problem in real life though. I can't see how it is allocating this much memory since even the profiler thinks that there's only 4MiB of data being stored.

Anyway, I'm putting memory optimization to one side, and am concentrating on what I hope are the final issues before releasing it:

Significant but easy:
Default piece graphics are tiny [fixed since posting]
Pieces don't resize.

Significant and hard:
Images don't display immediately once loaded.

Minor, may not fix yet:
On resizing the window, the background resizes slowly.
Textarea text is not selectable/copyable.
Default image backgrounds aren't transparent.


Click to Give Kudos
--

Yet another geek.


Re: Skinning on its way.

Admin

Solved:
x Diagonal drawing of the guidelines is mucked up.
x Vertical centring is ignored when drawing diagonally.
x Board sizes are ignored - why?
X components aren't transparent
X Out of memory, most likely due to too many threads.
X Sounds do not play on board
X Default piece graphics are tiddly.
X Board doesn't resize with the window.
X Board squares don't resize with the window.
X Board layout doesn't resize unti rotated.
X Board layout doesn't use padding until rotated.
X Mouse to BoardPos highlight/selection doesn't use padding.
X Piece redraw rectangle is misaligned.
X Pieces don't resize.
X Pieces are misaligned in the square at extreme sizes
X Pieces aren't drawn within the insets area.
x Diagonal guidelines break when horiz padding=0
X Insets should embed itself in any existing padding.
X Insets aren't respected or used.
X Piece alignment breaks when horiz padding=0
X Diagonal piece layout applies topPadding twice.
X Pieces don't resize properly when using the minimum size.

Significant and hard
? Images don't display immediately once loaded.

Minor
On resizing the window, it takes time for the background to reappear.
Textarea text is not selectable/copyable.
Gridelines don't stop at the board edges.
Padding should probably scale as a percentage of prefsize, rather than being absolute.


Click to Give Kudos
--

Yet another geek.


Re: Skinning on its way.

Admin

Solved:
X Images don't display immediately once loaded.
X Guidelines shouldn't display once board images have loaded.
X Main chat should have a background - gives "offscreen null"

No critical bugs left.

I'm now within spitting distance of getting the next version public. I'm at the stage of uploading it to the server and seeing how well it works.

And I find that one rather large problem stands in my way. OK, it's another critical bug. Sad

See, the java thingy is looking in the current html path for the skin files. And the current path for the java file is www.thudgame.com/node - which doesn't exist, it's a virtual directory peopled from the database by the content management system.

So, I'll have to force it to look in another folder. Probably /files or /skins or somesuch.

Argh, why is nothing ever simple? Still at least java's URL object should let me do that fairly simply.

Hrm, "fairly" was clearly overoptimistic. To change the path of a URL, youcan't just do myURL.setPath("new/path"); because that would be too easy.

Instead you have to do:

String myPath = "new/path";
String tmpString = myURL.getFile();
int oldFileIndex = tmpString.lastindexOf("/");
if (oldFileIndex != -1) {
  myPath += tmpString.substring(oldFileIndex);
}
myURL.set(
  myURL.getProtocol(),
  myURL.getHost(),
  myURL.getPort(),
  myPath,
  myURL.getRef()
);

Now, sure, you could do that in one line, but it would be pug ugly.

Mind you the above is pretty damn ugly too. Geesh, sometimes I despise this language.

[Edit: and of course, the above wouldn't work anyway, unless you can *guarantee* that the last thing on the url is a filename that you want to keep. If it's a directory or something, then it should be removed. So, the above is really an over-simplification.]

[Edit2: ...oh, no, that function is protected, you can't use it after all. In fact, you can't change URLs at all, you have to create a new one, like so:

String myPath = "new/path";
String tmpString = myURL.getFile();
int oldFileIndex = tmpString.lastindexOf("/");
if (oldFileIndex != -1) {
  myPath += tmpString.substring(oldFileIndex);
}
try {
  myURL = new URL(
    myURL.getProtocol(),
    myURL.getHost(),
    myURL.getPort(),
    myPath,
  );
}
catch (java.net.MalformedURLException e) {
  System.out.println("Bad URL!");
}

Isn't Java great?]


Click to Give Kudos
--

Yet another geek.


Re: Skinning on its way.

Admin

Hi! Who are you? :)You're reading my dev blog.

I like people who do that Very Happy

So as a li'l present, here's a link to the new client, uploaded and maybe-working, but not tested because I want to sleep.

Please let me know if it works Smile


Click to Give Kudos
--

Yet another geek.


Re: Skinning on its way.

it worked,but i didn't have anyone to play against,so can't tell if the actual board ran well.not many people look at this forum,maybe you should set up a time soon when we can try it out,so all of us know when to be on and we can all test it out for you


Click to Give Kudos
--

_O_
ll( )ll
_] [_


Re: Skinning on its way.

Admin

Hrm. Yeah, that's better than my original plan, which was to just test it a *little* bit, then put it live, then when people said "hai it no wurkz" I'd shout "hahalol sux2bu!" and run away.

I guess I should do some cunning log analysis and find which part of the day I'm prettymuch guaranteed people online, and ask people to test it then! Smile

Ah, but our server logging doesn't have timestamps. *rewrites logging to be more modular and cool* OK, now it does.

[Edit: Incidentally, this is why I hate Java but love PHP. PHP makes stuff like that dreamy-simple. I want to rewrite all my logging stuff from scratch, changing 300 lines of code? No problem, ten minutes' work, works first time, no knock-on effects.]

[Aside: should that be "ten minutes' work", "ten minute's work" or "ten minutes work"? I feel that it's a plural possessive (like: "the work of ten minutes"), so should be the former: but Google disagrees with me]


Click to Give Kudos
--

Yet another geek.


Re: Skinning on its way.

Founding PatronLibrarianDruidThudmeister

As far as your grammatical question goes, I'd definately agree with the "ten minutes' work" - google knows nothing Wink

MS


Click to Give Kudos
--

"LOOKS PERFECTLY LOGICAL TO ME"


Re: Skinning on its way.

ten minutes' work???
hmmm,ten minutes work
ten minutes' work
hmmmmmmmmm
it would seem to be possessive,wouldn't it.
although on the other hand can minutes possess something?


Click to Give Kudos
--

_O_
ll( )ll
_] [_


Re: Skinning on its way.

Admin

Well, my sister reckons we're all right too, and she's my own personal Ultimate English Language Authority on account of being a poet and knowing what rules to break when.

She was on the front page of a national newspaper the other day, you know!

(The newspaper is called "Mental Health Wales". Yes, that's the honest truth, and sums up my family quite well.)

Think I'll announce that new version in the announcements forum.


Click to Give Kudos
--

Yet another geek.


Re: Skinning on its way.

congrats to her,and am glad we have such an expert to lead us on our grammatical quests


Click to Give Kudos
--

_O_
ll( )ll
_] [_


Re: Skinning on its way.

Druid

i am learning java at uni to! i have been learnin for 8 weeks now! and what can i do?

sod all.

Crying or Very sad i cant remember any of it Sad

my ears bleed everytime i hear the "j" word


Click to Give Kudos
--

+++divide by cucumber error+++please reinstall universe and reboot+++


Re: Skinning on its way.

even when it is said with regards to coffee???


Click to Give Kudos
--

_O_
ll( )ll
_] [_


Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.