English Amiga Board


Go Back   English Amiga Board > Coders > Coders. General

 
 
Thread Tools
Old 19 April 2021, 15:32   #1
pink^abyss
Registered User
 
Join Date: Aug 2018
Location: Untergrund/Germany
Posts: 408
Wrong Way Driver - Tech

Some people wanted to hear more about how Wrong Way Driver was created.

Here are some details:

The game is written for OCS A500 + 512kb (tho with some extra work it would fit into 512kb only).

C++20
The game was born out of an experiment if it would be possible to code a fast Amiga 500 game
using C++20 with Coroutines and Lambdas. Bartmans "Amiga C/C++ Compile, Debug & Profile" which i used
has support for them. I guess this is the very first C++20 program written on any Amiga computer

Coroutines
Coroutines let you 'pause' or 'yield' your code and continue later at the same point. The context for
them is not stored on the stack but on a heap. Here is a pseudo example how i used them in the title screen
to move the logo, print some text and wait for the fire button in parallel by launching another coroutine.

Code:
coroutineHandler.add([&]() -> coro
{
   //move in the logo 
   for(int x=-160;x<160;x++)
   {
      showLogo(x);
      co_yield 0; //wait for next frame
   }

   co_yield 50; //wait 50 frames

   //add another coroutine to wait for fire button in parallel
   coroutineHandler.add([=]() -> coro
   {
       while(1)
       { 
          if(fireButtonPressed())
             gGame.setState(Game::State::intro);

          co_yield 0;
       }
   }

   //show blinking text
   while(1)
   {
      auto text=printText("Press Fire!);
      co_yield 20;
      clearText(text);
      co_yield 20;
   }
}
"coroutineHandler" is a container for the coroutines. The coroutineHandler is embedded in the current gameState and 'ticked' from there.
If i change the gameState then the coroutineHandler gets deleted. So i don't have to care about dangling coroutines.

Memory Managment
For this game i used again no dynamic memory managment (so no 'new' and 'delete', but only placement 'new'). I used preallocated Frame- and Poolheaps.
I described such heaps already for Tinyus Tech.

Sprites
Sprites are used for the "WRONG WAY DRIVER" and "ABYSS" logo and all HUD elements like the coin/gas counter and score display.
On the title screen i use 3 color sprites, and when playing the game i use attached 15 color sprites.
A single 128x224 sized area, with two bitplanes, is used for all sprites. A software driver sorts, splits, blits and multiplexes them into this
area each frame as needed. Writing an optimal sprite driver is a madmans task.

The city skyline
The city area uses dual playfield for parallax scrolling. Various copper splits are used to make it look
like more then two playfields. Also sprites are displayed above to make it look like 4 parallax layers are there.

The pseudo 3D street
The street is created with 5 parallaxed stripes. Each uses 16 frames of animation (3kb altogether packed).
They are blitted into the backbuffer and into the restorebuffer each frame.
The different cars, coins and gas symbols are just one image each. On startup i precalc 11 zoom images for each of them.
The street area runs in 4 bitplanes. I used two coppersplits to parallax animate the front and back side of the street.

Bobs
I use 16 and 32 pixel wide bobs for all zooming objects. Each bob can have a variable height.
For optimal performance i blit all of those after waiting for the last displayed line.

Audio
All insturments+fx take 34kb, and are precalced on startup (Pretracker!).
Channel 4 is used mainly for delay sounds and can be interrupted by SoundFx.

Game size
The game was crunched with Shrinkler from 135kb to to 61kb. The large size is mainly because of the used GCC optimizations (lto,-Ofast).
Without GCC optimizations the game is only half as big. By sprinkling #pragmas over non time critical code the size could be further reduced.


Thanks for reading.

Last edited by pink^abyss; 20 April 2021 at 15:02.
pink^abyss is offline  
Old 19 April 2021, 15:46   #2
Jobbo
Registered User
 
Jobbo's Avatar
 
Join Date: Jun 2020
Location: Druidia
Posts: 387
Very interesting to see co-routines in use on Amiga. I haven't used them myself in other projects but I've seen the concept used in games at least as far back as 1998.

What is the overhead like for yielding and continuing on the Amiga? Is the optimizer able to inline everything to such an extent that it only needs to save a partial context?
Jobbo is offline  
Old 19 April 2021, 16:33   #3
pink^abyss
Registered User
 
Join Date: Aug 2018
Location: Untergrund/Germany
Posts: 408
Quote:
Originally Posted by Jobbo View Post
Very interesting to see co-routines in use on Amiga. I haven't used them myself in other projects but I've seen the concept used in games at least as far back as 1998.

What is the overhead like for yielding and continuing on the Amiga? Is the optimizer able to inline everything to such an extent that it only needs to save a partial context?
Code generation within a coroutine follows the same rules as for any other function call. However, the coroutine itself can't be inlined. A coroutine yield will just restore registers and do a rts.
The code for iterating over and jumping into the coroutines adds also a little bit of extra work, but compared to just calling an array of function pointers and context its not much slower (at least when each of this functions also actually do some work).

Coroutines allow shorter, easier and much better readable code, for very little extra performance impact. Especially for typical 'throw away' game code that only happens once in your game without much structure.
pink^abyss is offline  
Old 19 April 2021, 21:28   #4
jotd
This cat is no more
 
jotd's Avatar
 
Join Date: Dec 2004
Location: FRANCE
Age: 52
Posts: 8,161
Bagman was probably the first amiga game to use C++11, you broke that record with C++ 20. I'm jealous.
jotd is offline  
Old 19 April 2021, 22:53   #5
DanScott
Lemon. / Core Design
 
DanScott's Avatar
 
Join Date: Mar 2016
Location: Tier 5
Posts: 1,211
Coroutines are banned where I work

Invoke too...
DanScott is offline  
Old 19 April 2021, 23:15   #6
Jobbo
Registered User
 
Jobbo's Avatar
 
Join Date: Jun 2020
Location: Druidia
Posts: 387
Please make them add the spaceship operator to that list!
Jobbo is offline  
Old 19 April 2021, 23:45   #7
jotd
This cat is no more
 
jotd's Avatar
 
Join Date: Dec 2004
Location: FRANCE
Age: 52
Posts: 8,161
spaceship operator what a joke, this is just useless...
jotd is offline  
Old 21 April 2021, 16:40   #8
pink^abyss
Registered User
 
Join Date: Aug 2018
Location: Untergrund/Germany
Posts: 408
Quote:
Originally Posted by Jobbo View Post
Very interesting to see co-routines in use on Amiga. I haven't used them myself in other projects but I've seen the concept used in games at least as far back as 1998.

What is the overhead like for yielding and continuing on the Amiga? Is the optimizer able to inline everything to such an extent that it only needs to save a partial context?
Regarding overhead for continuing a coroutine:
A jumptable call is used as indirection on each resume (if you have more then one co_yield). So the overhead compared to a normal function call is negligible (at least compared with a function that would need to save/restore all registers).

Another feature i didn't talk about was the use of lambdas in Wrong Way Driver. Those imposed no performance penalty and helped a lot to shorten the code and make it more readabale.
Lambdas are great for callbacks or short 'throw away' code snippets that you don't need in a function.
pink^abyss is offline  
Old 21 April 2021, 17:06   #9
Jobbo
Registered User
 
Jobbo's Avatar
 
Join Date: Jun 2020
Location: Druidia
Posts: 387
It's really interesting to hear these features can work effectively on a 68000.

I'm not sure how I would feel about opening this can of worms on a large project. But since most Amiga projects are tiny teams or just an individual it seems perfectly fine.

Certainly seems like it could simplify a lot of the high level sequencing code for a demo or game.
Jobbo is offline  
Old 21 April 2021, 19:50   #10
pink^abyss
Registered User
 
Join Date: Aug 2018
Location: Untergrund/Germany
Posts: 408
Quote:
Originally Posted by Jobbo View Post
It's really interesting to hear these features can work effectively on a 68000.

I'm not sure how I would feel about opening this can of worms on a large project. But since most Amiga projects are tiny teams or just an individual it seems perfectly fine.

Certainly seems like it could simplify a lot of the high level sequencing code for a demo or game.

I see your point (and i'm certainly not an advocate for modern C++ stuff). I used these things on Amiga 500 because i used them successfully already many years in my real world coding job.There we don't use coroutines in the game engine code, but we use lambdas a lot (we see them just as 'local' function calls). For our game scripting code (that drives the whole game) it's very different. There we use coroutines and lambdas (our own, not the C++ ones) all over the place. That made us writing games much faster and safer. We already shipped four games with them and are very happy with the results. The trick is to pack coroutines into hierachical objects and only operate on their own data. That makes it easy and safe to reason about them and their lifetime. The development time savings of using them in our games were mind-blowing.
pink^abyss is offline  
Old 22 April 2021, 13:30   #11
Bartman
Registered User
 
Join Date: Feb 2019
Location: Munich, Germany
Posts: 63
Here's an example of the generated assembly code from a trivial C++20 coroutine. Feel free to play around: Brown++ Compiler Explorer
Bartman is offline  
Old 23 April 2021, 14:19   #12
matburton
Registered User
 
matburton's Avatar
 
Join Date: Apr 2017
Location: Cambridge
Posts: 136
Thanks so much for this write-up!

I was expecting to read about graphical hijinx and assembly prowess, so C++20 and coroutines was a complete shock!

Quote:
Originally Posted by pink^abyss View Post
There we use coroutines and lambdas (our own, not the C++ ones) all over the place. That made us writing games much faster and safer.
Quote:
Originally Posted by pink^abyss View Post
That makes it easy and safe to reason about them and their lifetime.
I'm not one for hype, but since you used the words 'safe' and 'lifetime' what do you think about Rust? If the M68k LLVM backend becomes viable would it be appealing in any way?
matburton is offline  
Old 27 April 2021, 13:21   #13
pink^abyss
Registered User
 
Join Date: Aug 2018
Location: Untergrund/Germany
Posts: 408
Quote:
Originally Posted by matburton View Post
I'm not one for hype, but since you used the words 'safe' and 'lifetime' what do you think about Rust? If the M68k LLVM backend becomes viable would it be appealing in any way?

My comment was about our work environment, where we don't use C++ for scripting. With 'safe' & 'lifetime' i don't mean it in a strict way like Rust guarantees it, but simply how it behaves in our console game projects.
pink^abyss is offline  
 


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools

Similar Threads
Thread Thread Starter Forum Replies Last Post
Wrong Way Driver - New Amiga OCS Game by aBYSs pink^abyss News 75 18 December 2022 10:31
Tinyus Tech pink^abyss Coders. Asm / Hardware 84 06 April 2021 06:30
Trackmo tech paraj Coders. Asm / Hardware 4 30 March 2017 20:57
AmigaWorld Tech Journal Shadowfire AMR news 7 26 April 2009 19:14
LN2 tech demo gimbal project.Amiga Game Factory 63 02 October 2008 20:22

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT +2. The time now is 21:02.

Top

Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.
Page generated in 0.10655 seconds with 15 queries