catsfolly Posted December 17, 2014 Share Posted December 17, 2014 I'm trying to port a basic program over to IntyBasic. Naturally this basic program (written for another system) had no "wait" statement in its main loop, so when I translated it initially my IntyBasic version had no wait statements. The interesting thing was - it ran just fine. Controllers were read, sprites were displayed, it ran fairly smoothly, etc (I haven't put in any sound effects yet, maybe that will give me trouble...) I tried adding a "wait" to the main loop, and naturally the program ran slower... Can I live without a wait statement? Interrupts are apparently still happening and doing their things - I just can't predict exactly when they will happen... Catsfolly 1 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/ Share on other sites More sharing options...
+DZ-Jay Posted December 17, 2014 Share Posted December 17, 2014 Personally, I think that is the superior model: you shouldn't care about the interrupt request, other than to synchronize during initialization. Your game should just react when input events happen--whenever they happen--rather than break and wait for them. As a matter of fact, that's the model I am encouraging for P-Machinery games: you update your sprites' and animations' logical state, and let the framework update them whenever that happens. It allows the underlying engine to pipeline the changes if necessary. If you need to update the screen mode or do something that can only happen during VBLANK, such as updating the Color Stack, then you may want to wait. However, I would posit that such changes happen at "phase boundaries" rather than in the middle of game-play, and therefore follow under the "synchronize with ISR during initialization." -dZ. 1 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135133 Share on other sites More sharing options...
intvnut Posted December 17, 2014 Share Posted December 17, 2014 If you need to update the screen mode or do something that can only happen during VBLANK, such as updating the Color Stack, then you may want to wait. I think most of those things are handled by IntyBASIC keywords anyways, such as MODE. I think there was at least one keyword (MODE, in fact) that requires a WAIT after it, but that's usually limited to an initialization step, not a main game loop. Also, if you're dynamically changing graphics, WAIT ensures the graphics have been loaded before proceeding. Otherwise, the only issue I see with WAIT-lessness is that you don't really have a time base to reference your game against. So, your game will run faster on PAL machines than NTSC. (1MHz vs. 895kHz processors, and fewer cycles stolen by the STIC / retrace interrupts.) I experienced this directly w/ Kroz, adding a dummy delay loop to eat cycles on PAL to make the game the same speed as on NTSC. Maybe running faster is a good thing for some games, but in Kroz it made several rooms unplayable. What might be interesting is if IntyBASIC sprouted some ON xx GOSUB type commands, similar to QBASIC, for capturing async events. One of them could be an ON TIMER type deal that says, "After X time, call this subroutine." To make PAL and NTSC equivalent, pick a time base other than clock ticks, as 5 PAL ticks = 6 NTSC ticks. (You could also imagine an "ON COLLISION" and "ON KEY".....) 2 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135139 Share on other sites More sharing options...
catsfolly Posted December 17, 2014 Author Share Posted December 17, 2014 I'm guessing that my game in its current state is taking just a little longer than 1/60th of a second to run, and therefore adding a "wait" to the main loop forces it to run at 30 frames a second. Maybe with some optimization I can make it run fast enough that the wait won't make a difference... IntyBasic detects PAL or NTSC, and adjusts the music playing appropriately, but I don't think it adjusts anything else. The original version of Populous for the Amiga apparently had no synchronization with external clocks - it just ran as fast as it could. This was fine on the 8 MHz Amiga, but when they ported the game to the PC and PC clock speeds got faster and faster, the game became ridiculously unplayable... Catsfolly Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135141 Share on other sites More sharing options...
+DZ-Jay Posted December 17, 2014 Share Posted December 17, 2014 Also, if you're dynamically changing graphics, WAIT ensures the graphics have been loaded before proceeding. Perhaps a better approach would be to have a call-back on the interrupt event to allow the game to update state and synchronize itself. P-Machinery is event-driven by nature, so I take that for granted. My game loop reacts to game state changes rather than having to stop flow and wait. That's the biggest issue I have with the "WAIT" model. It's a clever solution to he technical limitations of the hardware, but it effectively *stops* flow completely until the next interrupt. It would be better if the wait could be localized to those tasks that require it. Call-backs could do that. What might be interesting is if IntyBASIC sprouted some ON xx GOSUB type commands, similar to QBASIC, for capturing async events. One of them could be an ON TIMER type deal that says, "After X time, call this subroutine." To make PAL and NTSC equivalent, pick a time base other than clock ticks, as 5 PAL ticks = 6 NTSC ticks. (You could also imagine an "ON COLLISION" and "ON KEY".....) I guess that's similar to the call-back approach I mentioned, but more "BASIC-y". I agree that this would be a good idea. dZ. Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135142 Share on other sites More sharing options...
+nanochess Posted December 17, 2014 Share Posted December 17, 2014 I tried adding a "wait" to the main loop, and naturally the program ran slower... Can I live without a wait statement? Interrupts are apparently still happening and doing their things - I just can't predict exactly when they will happen... Catsfolly You can live without the WAIT statement, because the video interrupt routine is seamless and everything depending on video is buffered (DEFINE, SPRITE, etc), the non-buffered ones would be SOUND and CONT? and also aren't affected. Although as other people pointed, you'll see your game runs slightly faster in PAL than NTSC, but besides that esentially your game will work fine and there are no worries about "over-clocked" Intellivisions out here If you want a time reference you can read the FRAME variable. 1 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135144 Share on other sites More sharing options...
intvnut Posted December 17, 2014 Share Posted December 17, 2014 If you want a time reference you can read the FRAME variable. Ah, forgot about that one. I was sure there was a way, but was in too big a hurry to check the docs. 1 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135152 Share on other sites More sharing options...
+nanochess Posted December 17, 2014 Share Posted December 17, 2014 What might be interesting is if IntyBASIC sprouted some ON xx GOSUB type commands, similar to QBASIC, for capturing async events. One of them could be an ON TIMER type deal that says, "After X time, call this subroutine." To make PAL and NTSC equivalent, pick a time base other than clock ticks, as 5 PAL ticks = 6 NTSC ticks. (You could also imagine an "ON COLLISION" and "ON KEY".....) I'm strongly tempted to implement ON FRAME GOSUB it would call a IntyBASIC subroutine after each ISR without need for WAIT. 2 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135172 Share on other sites More sharing options...
+5-11under Posted December 17, 2014 Share Posted December 17, 2014 I'm strongly tempted to implement ON FRAME GOSUB it would call a IntyBASIC subroutine after each ISR without need for WAIT. I like that idea. Reminds me of another console... . 1 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135186 Share on other sites More sharing options...
+DZ-Jay Posted December 17, 2014 Share Posted December 17, 2014 I'm strongly tempted to implement ON FRAME GOSUB it would call a IntyBASIC subroutine after each ISR without need for WAIT. I think that's appropriate. -dZ. 1 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135188 Share on other sites More sharing options...
freewheel Posted December 17, 2014 Share Posted December 17, 2014 I can tell you with certainty that some things require WAIT, with very specific timing constraints. One of them is scrolling. Here's some sample code from scroll.bas: REM >>>> Scroll to left - leave in in case you get stupid when you need to implement thisFOR time=1 to 240 IF offset_x=7 THEN offset_d=1:offset_x=0 ELSE offset_x=offset_x+1 SPRITE 0,64+$600-offset_x,48+$0100-offset_y,$0816 SCROLL offset_x,offset_y,offset_d WAIT offset_d=0 IF offset_x=0 THEN COL=0:GOSUB clear_column:PRINT AT RAND%12*20,"\257 NEXT time The SCROLL, WAIT, clear_column ordering is extremely important here, to ensure smooth scrolling. I have played with this in just about every other sequence imaginable and it really, really messes things up if you don't follow this sequencing. In spectacular ways at times but mostly just odd video artifacting. I'm not sure I see the issue for most games. Perhaps I'm thinking very "Atari-like" (or another platform), but everything video-wise is sync'd to VBLANK anyway, which is your basic 60 cycles per second timer in a sense. So I structure everything around that. Do whatever is necessary to set up your display, WAIT (to make sure everything takes effect), then proceed. Basic calculations, depending on what they affect, can happen before or after the WAIT. Maybe I just haven't written anything that needs faster computation. I do run into weirdness with not calling WAIT before checking the keypad which is why I don't really use it during gameplay. I'm not sure what improvements happened for IntyBASIC 1.0.1 on that front; I haven't tried it out yet. Consider this another vote for ON FRAME GOSUB. I basically have to handle all the timing manually right now. Easy enough, but if the language did it for me, all the better. 2 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135342 Share on other sites More sharing options...
catsfolly Posted December 18, 2014 Author Share Posted December 18, 2014 Good point about scrolling. It does require precise synchronization with the interrupt and video frame. Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135396 Share on other sites More sharing options...
freewheel Posted December 18, 2014 Share Posted December 18, 2014 Some day I'd love for one of you to explain to me some of the finer details of what's going on with WAIT and VBLANK and what commands do what in relation to these. The "problem" with IntyBASIC is that it makes it so damned easy to just slap together code and make something workable, without truly understanding (yeah you can look at the generated assembly, but who does that?). I've figured out much of what I know via trial and error. Code, compile, experiment, just like a university student. And correlating it back to documentation that is written for people who already understand much of this stuff. But then I run into something like the timing with scrolling, scratch my head for a while, and realize that I don't actually understand much of what the hardware is doing. What's weird is that I almost understand the 2600 better and I've barely touched code for it. Maybe because Racing the Beam was so damned detailed and yet plain language. 2 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135425 Share on other sites More sharing options...
intvnut Posted December 18, 2014 Share Posted December 18, 2014 (edited) (yeah you can look at the generated assembly, but who does that?). Who has two thumbs and reads compiler output for fun? This guy. ;) I've figured out much of what I know via trial and error. Code, compile, experiment, just like a university student. And correlating it back to documentation that is written for people who already understand much of this stuff. But then I run into something like the timing with scrolling, scratch my head for a while, and realize that I don't actually understand much of what the hardware is doing. What's weird is that I almost understand the 2600 better and I've barely touched code for it. Maybe because Racing the Beam was so damned detailed and yet plain language. It's kinda interesting. On the Atari 2600, you literally had to know where your raster was at all times to make a correct game. (Ok, they had a register you could punch that would halt the CPU and get you back in sync on the next horizontal retrace. Oooh.) That, and there was maybe less to learn about. You didn't have display modes and character generators and stuff. You had to write your graphics kernel, and it had in it exactly whatever you needed for the game you're making. Your main interface to the display was the single line raster buffer and a handful of "missile graphics" hardware sprites, as I recall. (If any Atari programmers are reading this, I apologize if I over-simplified things.) On the Intellivision, it's much more decoupled, with more of the system managed by hardware. And, that decoupling comes at a cost. You have higher level hardware abstractions such as character tiles and retrace interrupts. You have periods of time when you can't access certain display registers because the hardware takes over generating the display. And yet, you can't ignore the raster scan for certain operations like scrolling, otherwise you get tearing or other artifacts. The hardware abstractions aren't very deep abstractions and are quite leaky. :-) Edited December 18, 2014 by intvnut 1 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135436 Share on other sites More sharing options...
+DZ-Jay Posted December 18, 2014 Share Posted December 18, 2014 Who has two thumbs and reads compiler output for fun? This guy. ;) Let me see... I guess me too! 1 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135449 Share on other sites More sharing options...
freewheel Posted December 18, 2014 Share Posted December 18, 2014 (edited) The hardware abstractions aren't very deep abstractions and are quite leaky. :-) And it's a real blast to use a BASIC on something like this, and expose this sort of thing. I mean, I'm not exactly going to create new and wonderful programmer tricks in BASIC that blow people's socks off, but I've been surprised at just how close to the metal it is. And how many tricks I've been able to pull off. You just don't expect that from a BASIC on an 80s platform - well, without a lot of PEEKs and POKEs, which I use very rarely in IntyBASIC. My last compiler theory course and books are more years behind me than I care to think, but I'm getting a sense of how nanochess implements things. And it's nice to not be *too* abstracted away from the hardware, for when those edge cases come up. It makes it a lot easier to debug, when I'm too lazy to look at the compiler/assembler outputs. When you combine the two, you get a very easy, and yet very powerful platform. And as I've commented before, very dangerous. Way fun. Edited December 18, 2014 by freeweed 1 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135531 Share on other sites More sharing options...
intvnut Posted December 18, 2014 Share Posted December 18, 2014 (edited) And it's a real blast to use a BASIC on something like this, and expose this sort of thing. I mean, I'm not exactly going to create new and wonderful programmer tricks in BASIC that blow people's socks off, but I've been surprised at just how close to the metal it is. And how many tricks I've been able to pull off. You just don't expect that from a BASIC on an 80s platform - well, without a lot of PEEKs and POKEs, which I use very rarely in IntyBASIC. My last compiler theory course and books are more years behind me than I care to think, but I'm getting a sense of how nanochess implements things. And it's nice to not be *too* abstracted away from the hardware, for when those edge cases come up. It makes it a lot easier to debug, when I'm too lazy to look at the compiler/assembler outputs. When you combine the two, you get a very easy, and yet very powerful platform. And as I've commented before, very dangerous. Way fun. I get the sense that this BASIC is closer to the FORTRAN compilers of the 80s than the BASIC interpreters of the 80s. Notice we don't have string variables in this BASIC, and that arrays and DATA statements have a very direct binding to addresses. (I say FORTRAN only because BASIC syntax is closer to FORTRAN than to C or Pascal, but really the observation applies to all of those.) That is to say, to pull out some programming language jargon: IntyBASIC doesn't really do any late binding. Like a FORTRAN (or C or Pascal) compiler it binds everything at compile time, eliminating all the tracking data structures for variables, dynamic strings, garbage collection, line numbers, etc. It doesn't look up anything at run time by name. All the addresses get figured out at compile time. That's why it feels so much closer to the metal than a traditional Microsoft-ish BASIC. It doesn't put this generic interpreter bubble around you, providing floating point on an integer machine and garbage collected dynamic strings, and so on. GOSUB pushes an actual instruction address directly on the processor stack, not a line number or token address on some interpreter stack that has to be looked up on a RETURN. IntyBASIC is fairly efficient. It's easily just as efficient as naively / straightforwardly written assembly code, and likely more so in many cases. There are cases where you could be much, much more efficient with native assembler, but that would require careful thought and optimization to make it happen. I believe IntyBASIC's strategy is what the Dragon Book calls "syntax directed translation," although nanochess can correct me if I'm wrong. You can point at a line in the BASIC source code and the assembly that was generated for that line. Each syntactic element has a corresponding piece in the output. And what IntyBASIC's demonstrated is that the result is actually quite acceptable. You could maybe get some modest improvements if the compilation wasn't all line-by-line (ie. common subexpression elimination, dead store elimination, etc.), but those same optimizations can be done by hand in the BASIC program itself. The only way to get to a much higher level of performance is to start in assembly and make much more of the program state live in registers, not memory. And that doesn't really fit the BASIC programming model. :-) Edited December 18, 2014 by intvnut 1 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135551 Share on other sites More sharing options...
freewheel Posted December 18, 2014 Share Posted December 18, 2014 (edited) Your sense is what I've experienced. nanochess has pointed this out before, but you can actually see the line-by-line translation if you look at the generated asm. In some ways it reminds me of how early C++ just looked like a slightly higher level C in many respects. Objects were really just rolled up structs, etc. The analogy isn't perfect (nor is my overly-simplistic explanation), but it feels close. It's a big reason why I gravitated to it so quickly. Sure, I *could* write assembly directly if I had to (OK, admittedly it'd take me a few weeks to brush up) but I've always found it tedious. IntyBASIC takes a lot of the repetitive tedium away but still allows me to not worry about the language hiding things on me (or behaving weirdly at runtime). Edited December 18, 2014 by freeweed 1 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135567 Share on other sites More sharing options...
+DZ-Jay Posted December 18, 2014 Share Posted December 18, 2014 A programming language and a programming model are two distinct concepts, and I think the latter is still evolving in IntyBASIC. Personally, I like abstractions and a different programming model from what IntyBASIC currently offers. I think it is entirely possible to devise a framework for making games that affords high level abstractions, is generalized enough for diverse game applications, and is highly efficient--all at the same time. It's hard, but possible. I do not mean to take anything away from nanochess' accomplishment with IntyBASIC; it is a fantastic tool and has come a long way from it's first iteration. However, I do not think that a fast and efficiently compiled programming language necessarily precludes high level cognitive abstractions; the two are not mutually exclusive. For instance, I could absolutely see IntyBASIC integrated with something like P-Machinery, providing the expressiveness of the former with the power and versatility of the latter. -dZ. 1 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135646 Share on other sites More sharing options...
+nanochess Posted December 18, 2014 Share Posted December 18, 2014 Some day I'd love for one of you to explain to me some of the finer details of what's going on with WAIT and VBLANK and what commands do what in relation to these. The "problem" with IntyBASIC is that it makes it so damned easy to just slap together code and make something workable, without truly understanding (yeah you can look at the generated assembly, but who does that?). The VBLANK interruption does following: 0. Annotate interrupt happened. (used by WAIT) 1. Check for stack overflow (only if the statement for stack checking is used) 2. Change MODE 3. Change BORDER 4. Save collision variables 5. Update SCROLL registers 6. Update buffered sprites 7. Update sound registers 8. Redefine GRAM 1 (DEFINE) 9. Redefine GRAM 2 (DEFINE ALTERNATE) 10. Do SCROLL of screen 11. Call Intellivoice (if used) 12. Generate random number 13. Generate music 14. Increase frame number And WAIT does only one thing: 1. Wait for VBLANK to happen BTW, I'm another reader of the generated assembly just to check for optimization chances IntyBASIC is fairly efficient. It's easily just as efficient as naively / straightforwardly written assembly code, and likely more so in many cases. There are cases where you could be much, much more efficient with native assembler, but that would require careful thought and optimization to make it happen. I believe IntyBASIC's strategy is what the Dragon Book calls "syntax directed translation," although nanochess can correct me if I'm wrong. You can point at a line in the BASIC source code and the assembly that was generated for that line. Each syntactic element has a corresponding piece in the output. And what IntyBASIC's demonstrated is that the result is actually quite acceptable. You could maybe get some modest improvements if the compilation wasn't all line-by-line (ie. common subexpression elimination, dead store elimination, etc.), but those same optimizations can be done by hand in the BASIC program itself. The only way to get to a much higher level of performance is to start in assembly and make much more of the program state live in registers, not memory. And that doesn't really fit the BASIC programming model. :-) Yes, IntyBASIC uses the "syntax directed traslation" is the easiest one, the code is generated as the BASIC source is being read. In fact I learnt in the Spanish edition of the Dragon book how to create expression trees to generate optimized code for two-address machines So IntyBASIC does a lot of magic with expression trees, including constant expression optimization, reversing trees to save registers, accumulates additions and substractions and some other tricks. 2 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3135696 Share on other sites More sharing options...
intvnut Posted December 20, 2014 Share Posted December 20, 2014 (edited) The VBLANK interruption does following: 0. Annotate interrupt happened. (used by WAIT) ... 14. Increase frame number And WAIT does only one thing: 1. Wait for VBLANK to happen Hold on.... Why aren't 0 and 14 the same thing? If you always increase 'FRAME', then all wait has to do to detect an interrupt happened is something like this: . MVI _frame, R0 @@spin CMP _frame, R0 BEQ @@spin . That way you don't need to have a separate "waiting for interrupt flag" (_int in your code right now). Edited December 20, 2014 by intvnut 2 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3136899 Share on other sites More sharing options...
First Spear Posted January 1, 2015 Share Posted January 1, 2015 (edited) Akemashiteomedetōgozaimasu Akemashiteomedetōgozaimasu! Without getting into your business too much, what kind of BASIC (ie, machine platform source) were you using that you could port something into IntyBASIC and it mostly useful from the start? Thanks. I'm trying to port a basic program over to IntyBasic. [snip] Edited January 1, 2015 by First Spear 1 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3144027 Share on other sites More sharing options...
freewheel Posted January 1, 2015 Share Posted January 1, 2015 emashiteomedetōgozaimasu Without getting into your business too much, what kind of platform were you using that you could port something into IntyBASIC and it mostly useful from the start? Probably his pixel mario game A lot of old BASIC code would port well into IntyBASIC. You "only" have to deal with different I/O really, and a few other subtle nuances (WAIT, screen resolution, etc). Translate your controller inputs (which should be small), deal with any weirdness with screen size (and a lot of older systems were similar enough in this regard), and figure out how to re-do SPRITE/PRINT/etc commands. For a lot of simple games, an afternoon's work. nanochess has written IntyBASIC with a pretty vanilla syntax so it would hold up well. Heck even a lot of old Commodore BASIC, with all the POKEs and PEEKs snuck in, would probably port well if you remembered what all of those addresses and values did. Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3144036 Share on other sites More sharing options...
catsfolly Posted January 1, 2015 Author Share Posted January 1, 2015 (edited) Akemashiteomedetōgozaimasu Akemashiteomedetōgozaimasu! Without getting into your business too much, what kind of BASIC (ie, machine platform source) were you using that you could port something into IntyBASIC and it mostly useful from the start? Thanks. The program was "Super Pixel Brothers". If you look in the Super Pixel Brothers thread, you can find a link to the original source code and can download my IntyBasic source.. The original program was fairly well organized and commented, so it was mostly straightforward work to convert it. First, I figured out the data format, and wrote a C program to read in all the title and level data and generate Intellivision friendly data tables. Then, I wrote an IntyBasic test program to read the new data tables and display the title and levels on the screen. The main loop for the original program was just a bunch of calls to the routines that did everything, so I copied the main loop into my IntyBasic program and put "rem" statements in front of each call. Then I copied the routines over one by one, fixed them to run in IntyBasic, and then took out the rem for that routine in the main loop and tested it. The biggest problem was with the if statements. The original game was in Swordfish basic, which has multiple line if - then - else - endif statements. I had to change these into "spaghetti code" with lots of goto statements - not difficult but tedious to do... The original game has only a bitmap, so the player and bad guy and bullet are all drawn in the bitmap, and the bitmap is redrawn to the screen every frame. This made the game run slower than 60 frames a second, so adding a wait command made the game run slower, which started this thread ("Do I really need a wait command?") I changed the moving objects to be sprites, so the screen only had to be redrawn if it scrolled or if the player took out a block. This caused the game to run fast enough that adding a wait command didn't seem to slow it down. So now I am not wait-less... Catsfolly Edited January 1, 2015 by catsfolly 3 Quote Link to comment https://forums.atariage.com/topic/232875-intybasic-wait-less-ness/#findComment-3144070 Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.