Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
PDP-1
Oct 12, 2004

It's a beautiful day in the neighborhood.

My Rhythmic Crotch posted:

I guess I should keep developing the game and see if it warrants spending anything on "real" artwork.

I would do this if I were you. At the start of a new project artwork is the least of your worries, just use a generic tileset or some programmer art until you know that the game is actually going to get finished and be fun enough to warrant spending time/money on pretty pictures.


Internet Janitor posted:

[Pathing stuff]

Is the occasional Atan2() call really so expensive as to warrant a more complicated solution?

If you truly need to avoid trig you can still generate your "ideal" directions by comparing the relative values of dx and dy:

code:
if(dx>=0 && dy>=0)
{
  // first quadrant
  if(dx>2*dy) return East;
  elseif(2*dx<dy) return NorthEast;
  else return North;
}
else if(...)
{
  // other quadrants
}
Not 100% sure I nailed the logic there, but you get the idea.

PDP-1 fucked around with this message at 19:15 on Jan 22, 2012

Adbot
ADBOT LOVES YOU

The1ManMoshPit
Apr 17, 2005

Internet Janitor posted:

However, trig can be expensive.

What are you programming on, a calculator from the 1980s?

Anyways, you probably just want to use a line rasterization algorithm to get the next step in your path, something like this.

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."
PDP-1: Ah yes, so obvious in retrospect. Thanks:

code:
int quad(int dx, int dy, int a, int b, int c) {
  int ax = abs(dx);
  int ay = abs(dy);
  if (ax > 2*ay) { return a; }
  if (ay > 2*ax) { return c; }
  return b;
}

int ideal(int dx, int dy) {
  if (dx >= 0) {
    return (dy >= 0) ?
      quad(dx, dy, E, SE, S):
      quad(dx, dy, E, NE, N);
  }
  else {
    return (dy >= 0) ?
      quad(dx, dy, W, SW, S):
      quad(dx, dy, W, NW, N);
  }
}
The1ManMoshPit: Close. How about a VM modeled after game consoles from the 1980s, with no floating-point support? It's an entirely arbitrary limitation.

Edit: and hey! Check it out! That has a branchless closed form in Forth!

code:
:data quads	se e s	ne e n
		sw w s	nw w n

: ideal ( dx dy -- dir )
	over 0 < 6 and
	over 0 < 3 and + quads + >r
	abs swap abs
	over 2 * over < 1 and r> + >r
	     2 *      > 2 and r> + @
;
code:
final int[] quads = {SE, E, S, NE, E, N, SW, W, S, NW, W, N};
int ideal(int dx, int dy) {
  int ax = abs(dx);
  int ay = abs(dy);
  return quads[
    ((dx < 0)    ? 6 : 0) +
    ((dy < 0)    ? 3 : 0) +
    ((ax > 2*ay) ? 1 : 0) +
    ((ay > 2*ax) ? 2 : 0)
  ];
}

Internet Janitor fucked around with this message at 01:42 on Jan 23, 2012

Mr.Hotkeys
Dec 27, 2008

you're just thinking too much
Anyone have any good tutorials or articles on sweep and prune algorithms for 2D, or some books or something that cover it? The internet is being remarkably unhelpful.

duck monster
Dec 15, 2004

Internet Janitor posted:

The1ManMoshPit: Close. How about a VM modeled after game consoles from the 1980s, with no floating-point support? It's an entirely arbitrary limitation.


The way I'd deal with fast trig when I was making GBS threads around with assembler in the 80s trying to do wireframe 3D (hey man, battlezone was cool!) was to precompute sine + tangent tables (cosines just sine rotated around a phase), and store them so you could do something like LD A (SINEOFFSET + ANGLE) or however the gently caress the mneumonic went, and you'd get an answer in one or two clock ticks as long as you didnt give a gently caress about decimals.

PalmTreeFun
Apr 25, 2010

*toot*

duck monster posted:

The way I'd deal with fast trig when I was making GBS threads around with assembler in the 80s trying to do wireframe 3D (hey man, battlezone was cool!) was to precompute sine + tangent tables (cosines just sine rotated around a phase), and store them so you could do something like LD A (SINEOFFSET + ANGLE) or however the gently caress the mneumonic went, and you'd get an answer in one or two clock ticks as long as you didnt give a gently caress about decimals.

This still works, but you can often do a lot more approximations and add in linear interpolation between table values.

Suspicious Dish
Sep 24, 2011

2020 is the year of linux on the desktop, bro
Fun Shoe
Why not just add fast trig functions to your VM?

PDP-1
Oct 12, 2004

It's a beautiful day in the neighborhood.
If his VM is really designed to mimic an 80's era machine it most likely has an 8 bit bus and only recognizes integer and possibly BCD datatypes.

Before he could even get to trig functions he'd have to come up with a library to handle floating point data. This would mean defining some kind of data structure to hold a 32 bit float and then implementing addition, subtraction, multiplication, and division operators for it. Then you handle all the NaN, Inf, +/-0 and other special cases. All of this has to work on 32 bit data that can only be seen through an 8 bit window, so you gotta hop around between bytes and store whatever state you need in the very limited number of flags/registers available on those old machines. Only when all of that is done can you add fast trig functions to your VM. It's a big, painful job to implement IEEE style floats on old hardware and it would likely also violate the spirit of what he's trying to do.

I'm just ranting because I once had to write IEEE add/sub/mul/div routines in raw assembly for an 8085 class microcontroller. It was not a fun week.

OneEightHundred
Feb 28, 2008

Soon, we will be unstoppable!

PDP-1 posted:

Before he could even get to trig functions he'd have to come up with a library to handle floating point data.
Actually if you're trying to do that, this would be a good time to discover how C++ and makes fixed-point math staggeringly easier as long as you use multiply-with-overflow intrinsics (i.e. __emul and __emulu on x86, there's often no cost to these because it's what the processor does in the first place)

Most trig operations other than tan are very fixed-point friendly because they have known maximum values.

Scaevolus
Apr 16, 2007

It's a 32 bit machine. https://github.com/JohnEarnest/Mako/blob/master/src/MakoVM.java#L43

PDP-1
Oct 12, 2004

It's a beautiful day in the neighborhood.
Ah, ok. I saw his comment about it being "a VM modeled after game consoles from the 1980s, with no floating-point support" and assumed he wouldn't have that kind of thing available.

A lot of really old processors didn't have any native support for multiplication or division of any kind. You got add-with-carry and a subtract-with-carry opcodes that worked on 8 bits at a time plus some carry/borrow flags. Even if you only wanted to multiply two integers you had to write routines to do all the bit shifting and extended-length addition yourself.

Welp, it looks like I'm projecting my microcontroller induced PTSD onto Internet Janitor's project so I guess I'll just shut up.

mewse
May 2, 2006

PDP-1 posted:

Welp, it looks like I'm projecting my microcontroller induced PTSD onto Internet Janitor's project so I guess I'll just shut up.

writing your own ieee floating point routines in assembly is pretty hardcore, i wouldn't want to have to do it as a job responsibility tho

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."
PDP-1: No complaints here. Even if they aren't applicable to my problem, low-level coding war stories are always interesting.

duck monster
Dec 15, 2004

PDP-1 posted:

Welp, it looks like I'm projecting my microcontroller induced PTSD onto Internet Janitor's project so I guess I'll just shut up.

Gen Y are weak. Us C64 gen kids ate assembly for breakfast, and we LIKED IT.

Except for the fact girls wouldn't talk to us :smith:

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."

duck monster posted:

Gen Y are weak. Us C64 gen kids ate assembly for breakfast, and we LIKED IT.
Except for the fact girls wouldn't talk to us :smith:

Coding. Coding never changes. :smith:

Shalinor
Jun 10, 2002

Can I buy you a rootbeer?

duck monster posted:

Gen Y are weak. Us C64 gen kids ate assembly for breakfast, and we LIKED IT.
Hey, we late Gen X'ers did that too. It was just 80x86 ASM, possibly in a C ASM block.

Mode 13h was the bomb diggity. No one even knows what copper bars are, anymore :(

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."
Shalinor: I'll pour one out for the Original Chip Set. There's a reason they call 'em Copper bars, after all.

Piglips
Oct 9, 2003

I'm trying to set up a very basic SDL skeleton so that I can start experimenting with graphics. I just want to set a capped and steady framerate for rendering.

Basically I just have one single thread and an associated timer, and given the timer a UserEvent to signal a clock-tick.

code:
timer:
timercallbackfunction(clockevent):
  ·pushevent(clockevent);

mainthread:
// setup all variables and initialise the timer
  loop():
    ·empty out all the events in the queue looking for important ones
    ·do calculations
    ·empty out all the events in the queue looking for important ones
    ·if i didn't find the clockevent in the two polls of the queue beforehand, then start waiting on the event queue for events, until the clockevent's found
    ·do rendering
I've been running this and it seems to be responding okay, although occasionally when I measure the delta-times it can be a little unsteady. Is this event-pushing/rendering structure flawed and would I be better off using mutexes/signals/etc.? I originally had the main thread waiting on a condition variable and had the timer just signal that condition variable in its callback, but killed that idea because I wasn't entirely sure how the conditional variable signaling mechanism worked.

duck monster
Dec 15, 2004

Shalinor posted:

Hey, we late Gen X'ers did that too. It was just 80x86 ASM, possibly in a C ASM block.

Mode 13h was the bomb diggity. No one even knows what copper bars are, anymore :(

My first experiences on 8086 assembly scared the hell out of me. my first x86 machine was a Wang XP "compatible" and it had this bug-gently caress crazy hard drive.

I couldn't get minix to recognize the loving thing, so ended up having to write patches for the hard drive driver after spending about 3 weeks chasing down some *very* engrishy documentation from WANG.

The patch got very forcibly rejected by Tanenbaum, who basically didnt accept patches because of some reason I dont really know.

Just the addressing mode differences between the Z80s and the 8086s where confusing enough, but x86 architecture is like layers and layers of commands just patched over the tope of earlier ones. It was messy!

But at least I could run "unix", which was :cool: as hell, at the time.

Oh, and remember Herculese graphic cards? Yeah, theres a reason you don't.

e: actually they where pretty ballin' at the time. Its just that nothing loving supported them, so minix nerds had to try and roll our own :/

duck monster fucked around with this message at 14:26 on Jan 24, 2012

Rupert Buttermilk
Apr 15, 2007

🚣RowboatMan: ❄️Freezing time🕰️ is an old P.I. 🥧trick...

I wish I had had a C64 growing up. Programming, though I'm just starting and totally suck at it (actually, I'm only 'scripting', sorry!) really interests me.

Unormal
Nov 16, 2004

Mod sass? This evening?! But the cakes aren't ready! THE CAKES!
Fun Shoe
push AX, 13h
int 10h
!!!

duck monster
Dec 15, 2004

Unormal posted:

push AX, 13h
int 10h
!!!

One thing I always wondered, is why of all errors, divide by zero error got the prestige of having its very own interupt (interupt zero, in fact)

Also fun fact: Back in the DOS days, IBM computers always had a copy of BASIC squirreled away on a ROM chip, that could only be accessed by either ripping out the hard disk or firing off interupt 18h. It was actually a pretty neat version too. Much more C64ish than lovely qbasic.

PDP-1
Oct 12, 2004

It's a beautiful day in the neighborhood.

Piglips posted:

I've been running this and it seems to be responding okay, although occasionally when I measure the delta-times it can be a little unsteady.

How unsteady is unsteady? You'll always have a bit of framerate jitter due to the OS deciding to run your game thread at unpredictable intervals.

You can correct for it to some degree by adding a variable to your clock callback routine that tracks the exact amount of time since the last Update call. When the elapsed time is greater than your intended timestep you call Update and then reduce the time-tracking variable by exactly one timestep. Keep calling Update and reducing the time-tracking value until it is less than your timestep. This should leave some remainder time in the variable that will cause the next Update to happen slightly earlier and smooth things out a bit. You'll likely want to cap the time tracker at some value like 3-4 intended timesteps max so that big interruptions don't queue up thousands of Update events.

For example, suppose you start at t=0 and you want a 16ms frame time. If the first clock callback actually happens at 20ms, you put 20ms into your time tracker and then call Update since 20>16. Reduce the value in the time-tracker by one timestep to have a remainder of 4ms. Now the next Update call will happen slightly earlier, giving a better approximation of your intended framerate.

duck monster posted:

I couldn't get minix to recognize the loving thing, so ended up having to write patches for the hard drive driver after spending about 3 weeks chasing down some *very* engrishy documentation from WANG.

The patch got very forcibly rejected by Tanenbaum, who basically didnt accept patches because of some reason I dont really know.

Wasn't minix supposed to be a teaching tool OS? Tanenbaum may have rejected your patch if it complicated things in only a 'practical' way without adding value for a student trying to get into the subject.

Still, if you were contributing patches to minix back in that day then hats off to ya. In a thread with a Forth compiler and people waxing nostalgic over ASM coding you are still the biggest geek. :respek:

Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."
As a sort of epilogue for my pathfinding question, I talked about the problem with my office-mate, and we realized that we could remove the lookup table from my previous solution by arranging my boolean expressions to represent an encoding that would sum to an integer from 0 to 7, representing directions clockwise from north:
code:
int seek(int dx, int dy) {
  return
    ( ( 2*dx <    dy) ? -1 : 1)  *
    ((( dx   <  2*dy) ?  1 : 0)  +
     (( dx   > -2*dy) ?  1 : 0)  +
     ((-2*dx <    dy) ?  1 : 0)) +
    ( ( 2*dx <    dy) ?  7 : 0);
}
You can apply strength-reduction to remove some of those ternary operators (e.g, (dx < 2*dy) ? 1:0 becomes (-2*dy + dx) >>> 31 if we assume 32-bit words, since we're really just extracting the sign bit), but it's already completely impossible to understand what the code does, and Forth's choice of boolean encoding offers a cleaner solution.

Out of morbid curiosity, I compared the performance of my golfed-to-death seek() and an equivalent atan2()-based implementation in Java. 5 million calls takes 833 milliseconds for the trig-based version and 218 milliseconds for my mangled version, so for all that effort I achieved a four-fold speedup on an operation that is generally not a bottleneck. Woo.

Svampson
Jul 29, 2006

Same.
Question to you Unity people out there! (NO C64 HIPPIES ALLOWED)
I implemented a day/night cycle, which was relatively simple and straight-forward, the only problem is that trees in the distance doesn't reflect the lightning changes! I know this is because Unity Trees become billboards when far away as too not slaughter your framerate, but is there a way to recompute the lightning on the billboards or force it to generate new billboards on run time?

Only way I've found so far is moving the camera slightly to force it to update (this looks wierd) or turn the camera off and on (there is an obvious stutter when you do this), there has to be a proper way to do this!


Left is how it looks now and Right is how it is supposed to look

Shalinor
Jun 10, 2002

Can I buy you a rootbeer?

Svampson posted:

Only way I've found so far is moving the camera slightly to force it to update (this looks wierd) or turn the camera off and on (there is an obvious stutter when you do this), there has to be a proper way to do this!


Left is how it looks now and Right is how it is supposed to look
Bizarre. Isn't there a message you can send to the render component to fix that up? It seems like you should be able to pull up the list of functions, find the likely one, and just ping off a forced Update() call or whatever. (sorry, I don't know precisely what it is, I just feel like I've read about it before / like it should be right there in the front)

... that aside, if this were for some reason unsolvable, you could fix it via a bit of clever design too. Dead Rising masks its day->night transition with a cinematic, and you could do something similar if you were so inclined. Quick pulse to black then back, darkness dripping down the screen with a sound effect, whatever.

Shalinor fucked around with this message at 18:18 on Jan 24, 2012

PDP-1
Oct 12, 2004

It's a beautiful day in the neighborhood.
I don't know anything useful about Unity, but do you know how far away something has to be before it gets billboarded? If so, could you glue a floating quad to the camera that is just a smidgen closer than that distance, color it black, and then tweak it's alpha value in sync with your day/night cycle?

Rupert Buttermilk
Apr 15, 2007

🚣RowboatMan: ❄️Freezing time🕰️ is an old P.I. 🥧trick...

Svampson posted:

Question to you Unity people out there! (NO C64 HIPPIES ALLOWED)
I implemented a day/night cycle, which was relatively simple and straight-forward, the only problem is that trees in the distance doesn't reflect the lightning changes! I know this is because Unity Trees become billboards when far away as too not slaughter your framerate, but is there a way to recompute the lightning on the billboards or force it to generate new billboards on run time?

Only way I've found so far is moving the camera slightly to force it to update (this looks wierd) or turn the camera off and on (there is an obvious stutter when you do this), there has to be a proper way to do this!


Left is how it looks now and Right is how it is supposed to look

I would have to say that the absolute BEST way any game can transition from day to night, and back again, would be to pause the game against the player's will, have a huge text box pop up in the upper-right hand corner of the screen, and mention something (with really slow text) about an inconvenient curse.

Shalinor's way probably works too.

Shalinor
Jun 10, 2002

Can I buy you a rootbeer?

Rupert Buttermilk posted:

I would have to say that the absolute BEST way any game can transition from day to night, and back again, would be to pause the game against the player's will, have a huge text box pop up in the upper-right hand corner of the screen, and mention something (with really slow text) about an inconvenient curse.

Shalinor's way probably works too.
"Inconvenient curse" is a way better example of the approach, really, and fits the kind of low-fi look of the game. I like Rupert's idea better, and you'd get massive old-school props if you went that road.

Do that thing, it would be awesome.

Rupert Buttermilk
Apr 15, 2007

🚣RowboatMan: ❄️Freezing time🕰️ is an old P.I. 🥧trick...

Shalinor posted:

"Inconvenient curse" is a way better example of the approach, really, and fits the kind of low-fi look of the game. I like Rupert's idea better, and you'd get massive old-school props if you went that road.

Do that thing, it would be awesome.

Not according to the AVGN...

feedmegin
Jul 30, 2008

duck monster posted:

Gen Y are weak. Us C64 gen kids ate assembly for breakfast, and we LIKED IT.

Except for the fact girls wouldn't talk to us :smith:

Hey, some of us were writing ARM assembly before it was cool (Acorn Archimedes at school krew represent!)

Svampson
Jul 29, 2006

Same.

Shalinor posted:

Bizarre. Isn't there a message you can send to the render component to fix that up? It seems like you should be able to pull up the list of functions, find the likely one, and just ping off a forced Update() call or whatever. (sorry, I don't know precisely what it is, I just feel like I've read about it before / like it should be right there in the front)
The problem for me is accessing the trees, I'm not sure how to do that in Unity (Unity's trees doesn't act like other regular objects, doesn't seem to react to scripts I'm slapping on the tree prefab at least!)

PDP-1 posted:

I don't know anything useful about Unity, but do you know how far away something has to be before it gets billboarded? If so, could you glue a floating quad to the camera that is just a smidgen closer than that distance, color it black, and then tweak it's alpha value in sync with your day/night cycle?
This did the trick, thanks for the tip! :3:

And the world literally ends at midnight so that should fill the curse quota :colbert: (Or I could skip doing a fancy end of the world event and just have a message box that says "What a terrible night to have the world end" and then just exit the game)

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:

Svampson posted:

(Or I could skip doing a fancy end of the world event and just have a message box that says "What a terrible night to have the world end" and then just exit the game)

Just make the screen completely dark gray (about #222222) and stop responding to input.

Shalinor
Jun 10, 2002

Can I buy you a rootbeer?

Svampson posted:

(Or I could skip doing a fancy end of the world event and just have a message box that says "What a terrible night to have the world end" and then just exit the game)
Bingo.

Though I suppose if you did this, you'd want it to be a random chance or specific unlockable. Still, though, 'twould be awesome.

Svampson
Jul 29, 2006

Same.
Okay so turns out that didn't work, because alt+tabbing in and out of the game forces it to rerender everything (and thus updating the trees lightning) so everything behind the quad becomes pitch black

edit: Guess I could do a separate terrain for all the out of bounds trees and other background stuff and make that ignore lightning

Svampson fucked around with this message at 19:26 on Jan 24, 2012

Rocko Bonaparte
Mar 12, 2002

Every day is Friday!

duck monster posted:

One thing I always wondered, is why of all errors, divide by zero error got the prestige of having its very own interupt (interupt zero, in fact)
My speculation was that since Intel started out with microprocessors for calculating machines, it probably stuck in reuse going into x86. You could imagine them moving on from one project to the next that they'd want to make sure it can do its predecessor's stuff, so from the beginning of x86 they were probably dragging in 4004 stuff into it.

Rupert Buttermilk
Apr 15, 2007

🚣RowboatMan: ❄️Freezing time🕰️ is an old P.I. 🥧trick...

Anyone here have any experience with Miles Sound System? I've just started learning it today, and am wondering if I'm going to come up against any brain-leaking/frustrating 'gotchas' later on.

For example: Moving projects in FMOD.

Piglips
Oct 9, 2003

PDP-1 posted:

How unsteady is unsteady? You'll always have a bit of framerate jitter due to the OS deciding to run your game thread at unpredictable intervals.

You can correct for it to some degree by adding a variable to your clock callback routine that tracks the exact amount of time since the last Update call. When the elapsed time is greater than your intended timestep you call Update and then reduce the time-tracking variable by exactly one timestep. Keep calling Update and reducing the time-tracking value until it is less than your timestep. This should leave some remainder time in the variable that will cause the next Update to happen slightly earlier and smooth things out a bit. You'll likely want to cap the time tracker at some value like 3-4 intended timesteps max so that big interruptions don't queue up thousands of Update events.

Yeah that kinda makes sense, although I probably can't get the callback to push multiple events if it's particularly behind, because SDL only has a singular event-queue.

I was wondering whether it would be more efficient to break the timer out of the general event-queue and have it signal the main thread through a mutex+condition variable instead... however I'm not particularly good at visualising these race-conditions and can't think of a real-time signaling mechanism that isn't flawed somehow.

The method I'm using now is mostly steady under zero calculation/rendering load at a 30ms frame length, although it does occasionally wobble between 0ms-60ms. I'm assuming it could be improved a little by raising the process' priority, but SDL doesn't have an option to do that in its API.

Shalinor
Jun 10, 2002

Can I buy you a rootbeer?
As I am about to dive in head-first, does anyone happen to know of a Hello World-esque walkthrough of all of the back-end PHP et al that typically drives a web/Facebook game?

That is to say - I've written plenty of Flash games, but I've never written one with any sort of back-end. I'm finding bits and pieces of information, but this seems like it should be very well-traveled territory.

(EDIT: I realize stuff like this is out there; I'm looking more for something that includes the typical DB interaction you might do, posting to Walls, maybe Twitter integration, whatever, some selection of the common actions.)

Shalinor fucked around with this message at 23:02 on Jan 24, 2012

Adbot
ADBOT LOVES YOU

PDP-1
Oct 12, 2004

It's a beautiful day in the neighborhood.

Piglips posted:

I was wondering whether it would be more efficient to break the timer out of the general event-queue and have it signal the main thread through a mutex+condition variable instead... however I'm not particularly good at visualising these race-conditions and can't think of a real-time signaling mechanism that isn't flawed somehow.

This is pretty much what I ended up doing for my timing loop. The clock is a Win32 TimerQueueTimer that blocks the main game thread until some delta t has elapsed and signals when it's ok to proceed by releasing a shared dummy variable. Maybe there is a better way to do it, but on my system it stayed at 60 +/- 1FPS up to about 90% steady state processor load. The only thing that really tripped it up was stuff like Alt+tabbing through other applications or moving windows around to force lots of re-draw activity. That isn't much of a practical issue though, since you'd likely pause the game if it didn't have focus.

I did play with thread priority by bumping the game thread up one notch and pushing the worker thread pool down one notch. It helped a bit but wasn't a really radical improvement. One thing that did seem to help was intentionally sleeping the worker pool threads periodically. This gives the OS room to take care of whatever work it has queued up at a steady rate instead of letting it all pile up and then getting a spike of OS activity all at once.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply