Sunday, October 7, 2018

HBF, experimental version

I've been playing for a while with the attribute [unsafe], the reason is that I use interfaces all over the code but I manage my own objects, so I don't need this COM overhead, I need basically interfaces behaving as clever pointers.

The game offers to all entities the following interface which returns many interfaces to handle any part of the game behaviour, like this nobody is aware of the implementations of the classes.

  IGameServices = interface
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetSession: ISession;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetEvents: IEvents;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetPlayers: IPlayers;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetCoins: ICoins;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetMap: IMap;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetDrawer: IGraphicDrawer;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetGraphicInfo: IGraphicInfo;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetLog: ILog;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetProfile: IProfile;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetFont: IFont;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetSmallFont: IFont;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetWindow: IWindow;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetLayer(aIndex: integer): ILayer;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetCameraHelper: ICameraHelper;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetHud: IHud;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetEffects: IEffects;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetSound: ISoundDispatcher;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetMusic: IMusic;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetFileSystem: IFileSystem;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetOptimizer: IOptimizer;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetFactory: ISpriteFactory;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetBack: IBackground;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetSettings: ISettings;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetHighscore: IHighscore;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetLevels: ILevels;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetVars: IMemVar;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetAchievements: IAchievements;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetRunes: IRunes;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetShop: IShopHandler;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetTimer: ICrono;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetInput: IInput;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetCurrentScreen: IScreen;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetTrigger: ITriggerDispatcher;
    {$IFDEF _UNSAFE_}[result: unsafe]{$ENDIF}function GetChat: IChat;
  end;


So a typical enemy would do GameServices.GetSound.Play() in order to play a sound.

GameSprite.pas.4293: begin
00A6BA50 53               push ebx
00A6BA51 56               push esi
00A6BA52 8BF2             mov esi,edx
00A6BA54 8BD8             mov ebx,eax
GameSprite.pas.4294: Exit(FGameServices);
00A6BA56 8BC6             mov eax,esi
00A6BA58 8B93A4030000     mov edx,[ebx+$000003a4]
00A6BA5E E8D5739AFF       call @IntfCopy
GameSprite.pas.4295: end;
00A6BA63 5E               pop esi
00A6BA64 5B               pop ebx

00A6BA65 C3               ret 

The classic IntfCopy() is there and of course their counterparts InfClear(). When you have a system which continuously passes interfaces then is quite an overhead.

In the experimental version we get:

GameSprite.pas.2834: result := GameServices.GetSound.Play(aFileName, aLoop, True);
00A63218 6A01             push $01
00A6321A 8BC6             mov eax,esi
00A6321C E863390000       call TGameSprite.GetGameServices

GameSprite.pas.4293: begin
00A66B84 51               push ecx
GameSprite.pas.4294: Exit(FGameServices);
00A66B85 8B80A4030000     mov eax,[eax+$000003a4]
00A66B8B 890424           mov [esp],eax
GameSprite.pas.4295: end;
00A66B8E 8B0424           mov eax,[esp]
00A66B91 5A               pop edx

00A66B92 C3               ret 

Hooray!, all that overhead is gone. The problem is that the compliler very often makes a mess and the code explodes as an AV (I haven't found a pattern), so I change the way the calls are done and then it no longer crashes.

For example this crashes (in some places of the code some others not):

GameServices.GetPlayers.Kill(pOne);

changing the code  a bit as:

players := GameServices.GetPlayers;
players.Kill(pOne);

Not a big deal but quite cumbersome to do all over the code, so I change it when it crashes.

Still not convinced?

Performance test with classic interfaces:



About 40 FPS for 50000 triangles.

Now with this UNSAFE stuff:



Almost 48 FPS for 50000 triangles.

I believe if worth the effort. It is important to note that interfaces should all be declared as const and never as inline.

So once I'm happy with the results I will publish this version.

Cheers.

Friday, October 5, 2018

Summer is gone...

Well, still not bad weather but summer "mode" is gone...

Anyway, as usual I always find time to improve the framework (which is the main idea behind all this) and the game itself.

Main changes are performance boost here and there, framework improvements, game stability and a new secret level.



Found binaries at OneDrive, here is the what's new:

HBF)
  • All sounds are now played in 3d mode, panning and volume are continuously recalculated from hero
  • New class TChat 
  • Hero can sell items 
  • New class TColorTileHandler
  • Some lasers throw sparks when collide with a block
  • New trigger functions: IsDoorOpen(), SkipIfTrue() and SkipIfFalse()
  • Inventory had a memory leak, fixing that memory leak lead to many issues...
  • Now when throwing a weapon another one can be choosen and the old one still visible
  • Inventory now internally uses TStrings<T>
  • TShopHandler.Remove() is now faster 
  • Gift stuff removed, now they are invisible
  • Items now can also be sell
  • Shop/Inventory items code cleaned a bit
  • Platforms graviting was detecting itself...
  • Now when handling collision with blocks over a tape, tape speed is taking into account
  • New Headshot property distance 
  • New shop item Invisibility
  • All entities that could react to the presence of hero now take into account Invisibility
  • Teleport system rewrited, it was not handling properly the guests
  • Now boomerang distance is configurable
  • Shop shows information about items
  • New red boomerang (level 2)
  • Game now distinguish between vertical and horizontal slopes so everything is easier
  • Horizontal slopes take into account Facing property
  • Spider runs away when hero has immunity
  • New TSelf that reflects entities
  • New Mirror.Alignment property
  • Now arrow has a power = 1
  • Now stone has a power = 2
  • Now golden shield can stop lasers
  • Property Chest.Z changed
  • Left and Right collisions with slope tiles improved a bit
  • Now when shopping game shows the owned quantity of each item
  • A door can connect to other door from different level 
  • When connecting doors selection screen is not shown anymore
  • When selecting the next level, a sound is played
  • Now TLiquid implements IExecutor
  • Now Rotator can have different colors with ColorMaskA/B
  • Now rotator uses two light colors
  • Rotator now has a counter property so start Angle can be controlled
  • Now laser properly bounds in Mirrors
  • New class TFlyingEnemy
  • Now weapons can have a quantity (so bow does not have infinite arrows for instance)
  • Now Shop offers Ammo, a new shop item
  • Now any enemy can have wings and fly 
  • New aligments acBottomLeft and acBottomRight
  • Time shader off was not working
  • New Bomb bitmap
  • Now droid has a new laser color property 
  • New trigger CallElevator()
  • Trigger ItemOffset() renamed to TileOffset()
  • New trigger method MotionItem()
  • Unknown level is finished
  • Now teleport connection is per name and not per group, so teleports can freely connect to any teleport
  • Now Teleports can be disabled 
  • Now Teleports now can also handle enemies 
  • Door now can be closed automatically after open it with property CloseAfter
  • Door now can be half open but not accessible
  • Platforms can be executed from scripts 
  • Platforms bouncing adjusted at pixel level 
  • Now triggers and their timings are script dependent internally
  • Now weapons are used in grid collision matrix 
  • Improved code like "interface as TClass" with "TClass(interface.AsObject)" (seems to be faster)
  • Now when you get a coin you get a message with the number of coins 
  • Certain coins now use a Distributed Random Number Generator
  • Slots and Sounds per Slot are now configurable within the SoundDispatcher class
  • Sword cannot longer break walls, use bombs (hint...)
  • Extras code improved
  • New item property IsContinuous
  • Now monologue is now shown when in shop or inventory screen 
  • Graviting is done with walking animation
  • New inventory function ActivateOnlyOne()
  • Now inventory can be navigate it with up and down keys
  • Clear inventory was not working properly after the first game 
  • Fixed an issue pushing items recursively
  • In game menu now will always start with index 0 no matter how many times you access it
  • Now menus can be locked 
  • Now game fades properly when exiting game 
  • TBell renamed to TBells to avoid collision with extra.TBell 
  • Now triggers can have required items 
  • Countdown now has a direction property
  • Lamp has new style
  • Now game can be paused when fading
  • Fixed lamp light priority
  • TInventory.ActivateFirst() is now faster
  • Fixed error in magnet, when trying to read non IGameSprite interfaces and then AV
  • Map light is now blue
  • Pushable stuff now does slippery properly
  • GameServices.EnterInventory() moved to GameServices.GetPlayers.GetInventory.Enter()
  • GameServices.EnterShop() moved to GameServices.GetShop.Enter()
  • New TGameSprite properties MinAngle/MaxAngle to adjust the bounds of the gravity orientation 
  • TGameSprite.IsIce property removed (it was unused)
  • Countdown now uses Y for position
  • Spring weird movement fixed
  • Shield code moved to GameSprite 
  • Jumping force now uses property Power, so jumping enemies can have different jumping power 
  • TColumn can use now different sprite sets
  • Maximum camera zoom set to 1024
  • Fade step set to 4
  • Gravity orientation code refactored
  • Pushing objects use property Power 
  • Under some circumstances PushedByBlocks() got hero stuck
  • Shield can now stop fire bullets
  • Music timers are now local
  • Crepuscule is not a bit yellow
  • Now Pause key can also be redefined
  • When hero dies he also releases its chest keys
  • New class TRay
  • TCustomScreen refactored
  • Platforms can rotate with certain cadency
  • Game.GetTarget()/SetTarget() moved to CameraHelper
  • New trigger Sleep() (more complex than it sounds)
  • Ice can be sloped
  • Now any sprites supports multiple fellows
  • Now arrows can be called from the trigger system since they implement IExecutor
  • Shield and armor now takes into account hero's balance
  • Some maps now have floor traps in tiles
  • Fixed a bug when on top of ladder and jumping
  • New Scepter bitmap
  • New Explosion bitmap
  • Nulling some interfaces when destroying some entities
  • New helper Coins
  • New property TPlatform.SleepAfterBounce
  • Now message key can be set in the config 
  • Stalactite was not breaking when falling on top of a map item
  • Preparing screen code improved a bit
  • Gametrigger was not settting Script to nil when destroyed (AV)
  • Now there is a secret coins per map, jump to find it!
  • New TVanishPlatform
  • TRotatingPlatform fixed
  • Spring behavior improved
  • Top/bottom collisions are not checked when items are invisible
  • Left/Right collisions are done against rectangle and not BB
  • Liquid can now load any particle system
  • Now NEW_ATTRIBUTE is part of the non breakable attributes
BB)
  • New TFloatHelper.Abs(), faster by just clearing bit 31
  • Collition Matrix system improved a bit
  • TSurface.Blit(add) is now MMX 
  • New TRGB.Clamp() as MMX
  • Some buffer triangle functions writed as MMX
  • Now using TFloatHelper.Sqrt(), TFloatHelper.Trunc(), TFloatHelper.Round() which are MMMX optimized
  • Now TMatrixEx uses some MMX assembler optimizations
  • Now TVectorx uses MMX assembler optimizations
  • Removed inline from functions returning interfaces (Delphi makes a mess)
  • Removed inline when function result is string (bad optimization from compiler)
  • Cleaned functions that didn't need to be virtual
  • Sprite.Pause is no longer virtual (that was actually slowing down the game accoring to profiler)
  • New Sprite.CanPause property, useful when you want some sprites to never be paused
  • Improved sprite engine main loop by creating local vars of global vars and duplicating the loop with IF and not IF
  • New TObject.PopTag()
  • Now trigger calls must implement the Caller and script Name (this avoids some mess code)
  • New TLaser.CurrentSegments property 
  • For specific triangle routines Buffer.Tag is used rather than Shape.Tag
  • Now particles store the filename making debugging easier
  • New collection helper to sort as Insertion algorythm (good for almost sorted collections)
  • New YTLayer.GetCells() that works with map coordinates
  • TLayer.Trace() now works with map coordinates rather than pixel coordinates 
  • TSimpleSprite.CalculateQuad() reordered so that performance is improved
  • Fixed Z in Layer.AddItem()
  • TScript.RemoveWhenTaken removed, now use Counter = 1 (the default)
  • Now with RTTI system if enumerator does not exists an exception is raised
  • TEvents renamed to TEventEx
  • TEventEx loop forced to be forward (so calls order in scripts is respected, very important!)
  • TScript moved to its own unit
  • New LinkType property in triggers to allow more behaviors like mutual connexion
  • TStrings<T> is now used in TIni rather than TDictionary<K,V>
  • Scripts improved with TicksBetweenExecutions, Executor and Counter new properties
  • Distortions were not free properly
  • New TObjectEx.Error(), so that one does not need to do raise exception
  • TStateDirector<T>() improved a bit
  • Fading system improved a bit
  • property TSprite.FreeSurface removed (was always the negative of Cached)
  • Fixed a AV in Glow and Shadow surfaces  (double free)
  • Now when addings events if steps is zero then the CallBack is executed immediately
  • Fixed TMapInfo.HasTile(), my goodness...
  • Now script Input property can be any key
  • Fixed a silly typo error in collision library
  • New property TFont.Uppercase