Skip to content

Commit

Permalink
fix: ScreenAppender Logger (#2935)
Browse files Browse the repository at this point in the history
Closes: #2886

Fixes an issue spotted by @jyoung4242 where `ScreenAppenders` did not work properly. They are now also more configurable!


https://github.com/excaliburjs/Excalibur/assets/612071/9f971648-db2f-4958-bcb8-606c00c234ce



```typescript
const logger = Logger.getInstance();
const screenAppender = new ScreenAppender({
    engine: game,
    color: Color.Black,
    xPos: 0
});
logger.addAppender(screenAppender);
setInterval(() => {
    logger.info('test', performance.now());
}, 100);
```
  • Loading branch information
eonarheim authored Feb 17, 2024
1 parent a981c5d commit 00db29c
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 19 deletions.
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,32 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Fixed

- Fixed issue with Log ScreenAppender utility where it was not positioned correctly, you can now deeply configure it!
```typescript
export interface ScreenAppenderOptions {
engine: Engine;
/**
* Optionally set the width of the overlay canvas
*/
width?: number;
/**
* Optionally set the height of the overlay canvas
*/
height?: number;
/**
* Adjust the text offset from the left side of the screen
*/
xPos?: number;
/**
* Provide a text color
*/
color?: Color;
/**
* Optionally set the CSS zindex of the overlay canvas
*/
zIndex?: number;
}
```
- Fixed errant warning about resolution when using `pixelRatio` on low res games to upscale
- Fixes an issue where a collider that was part of a contact that was deleted did not fire a collision end event, this was unexpected
- Fixes an issue where you may want to have composite colliders behave as constituent colliders for the purposes of start/end collision events. A new property is added to physics config, the current behavior is the default which is `'together'`, this means the whole composite collider is treated as 1 collider for onCollisionStart/onCollisionEnd. Now you can configure a `separate` which will fire onCollisionStart/onCollisionEnd for every separate collider included in the composite (useful if you are building levels or things with gaps that you need to disambiguate). You can also configure this on a per composite level to mix and match `CompositeCollider.compositeStrategy`
Expand Down
79 changes: 60 additions & 19 deletions src/engine/Util/Log.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
/* eslint-disable no-console */

import { Engine } from '../Engine';
import { vec } from '../Math/vector';
import { Color } from '../Color';

/**
* Logging level that Excalibur will tag
*/
Expand Down Expand Up @@ -230,28 +235,64 @@ export class ConsoleAppender implements Appender {
}
}

export interface ScreenAppenderOptions {
engine: Engine;
/**
* Optionally set the width of the overlay canvas
*/
width?: number;
/**
* Optionally set the height of the overlay canvas
*/
height?: number;
/**
* Adjust the text offset from the left side of the screen
*/
xPos?: number;
/**
* Provide a text color
*/
color?: Color;
/**
* Optionally set the CSS zindex of the overlay canvas
*/
zIndex?: number;
}

/**
* On-screen (canvas) appender
*/
export class ScreenAppender implements Appender {
// @todo Clean this up

private _messages: string[] = [];
private _canvas: HTMLCanvasElement;
public canvas: HTMLCanvasElement;
private _ctx: CanvasRenderingContext2D;
private _pos = 10;
private _color = Color.Black;
private _options: ScreenAppenderOptions;
constructor(options: ScreenAppenderOptions) {
this._options = options;
this.canvas = <HTMLCanvasElement>document.createElement('canvas');
this._ctx = this.canvas.getContext('2d')!;
this.canvas.style.position = 'absolute';
this.canvas.style.zIndex = options.zIndex?.toString() ?? '99';
document.body.appendChild(this.canvas);
this._positionScreenAppenderCanvas();
options.engine.screen.events.on('resize', () => {
this._positionScreenAppenderCanvas();
});
}

/**
* @param width Width of the screen appender in pixels
* @param height Height of the screen appender in pixels
*/
constructor(width?: number, height?: number) {
this._canvas = <HTMLCanvasElement>document.createElement('canvas');
this._canvas.width = width || window.innerWidth;
this._canvas.height = height || window.innerHeight;
this._canvas.style.position = 'absolute';
// eslint-disable-next-line
this._ctx = <CanvasRenderingContext2D>this._canvas.getContext('2d'); // eslint-disable-line
document.body.appendChild(this._canvas);
private _positionScreenAppenderCanvas() {
const options = this._options;
this.canvas.width = options.width ?? options.engine.screen.resolution.width;
this.canvas.height = options.height ?? options.engine.screen.resolution.height;
this.canvas.style.position = 'absolute';
const pagePos = options.engine.screen.screenToPageCoordinates(vec(0, 0));
this.canvas.style.left = pagePos.x + 'px';
this.canvas.style.top = pagePos.y + 'px';
this._pos = options.xPos ?? this._pos;
this._color = options.color ?? this._color;
}

/**
Expand All @@ -262,17 +303,17 @@ export class ScreenAppender implements Appender {
public log(level: LogLevel, args: any[]): void {
const message = args.join(',');

this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
this._ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

this._messages.unshift('[' + LogLevel[level] + '] : ' + message);

let pos = 10;
let opacity = 1.0;

this._messages = this._messages.slice(0, 1000);
for (let i = 0; i < this._messages.length; i++) {
this._ctx.fillStyle = 'rgba(255,255,255,' + opacity.toFixed(2) + ')';
this._ctx.fillText(this._messages[i], 200, pos);
this._ctx.fillStyle = this._color.toRGBA();
this._ctx.fillText(this._messages[i], this._pos, pos);
pos += 10;
opacity = opacity > 0 ? opacity - 0.05 : 0;
}
}
}
46 changes: 46 additions & 0 deletions src/spec/ScreenAppenderSpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as ex from '@excalibur';
import { TestUtils } from './util/TestUtils';
import { ExcaliburAsyncMatchers } from 'excalibur-jasmine';

describe('A ScreenAppender', () => {
beforeAll(() => {
jasmine.addAsyncMatchers(ExcaliburAsyncMatchers);
});

it('exists', () => {
expect(ex.ScreenAppender).toBeDefined();
});

it('can be constructed', () => {
const engine = TestUtils.engine({width: 100, height: 100});
const sut = new ex.ScreenAppender({engine});
expect(sut).toBeDefined();
engine.dispose();
});

it('can be configured to log', async () => {
const engine = TestUtils.engine({width: 100, height: 100});
const sut = new ex.ScreenAppender({
engine,
color: ex.Color.Yellow,
xPos: 0
});

sut.log(ex.LogLevel.Info, ['this is a log']);
sut.log(ex.LogLevel.Info, ['this is a log']);
sut.log(ex.LogLevel.Info, ['this is a log']);
sut.log(ex.LogLevel.Info, ['this is a log']);
sut.log(ex.LogLevel.Info, ['this is a log']);
sut.log(ex.LogLevel.Info, ['this is a log']);

await TestUtils.runOnWindows(async () => {
await expectAsync(sut.canvas).toEqualImage('src/spec/images/ScreenAppenderSpec/screen-log.png');
});

// FIXME CI chokes on this one for some reason
// await TestUtils.runOnLinux(async () => {
// await expectAsync(sut.canvas).toEqualImage('src/spec/images/ScreenAppenderSpec/screen-log-linux.png');
// });
engine.dispose();
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/spec/images/ScreenAppenderSpec/screen-log.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions src/spec/util/TestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,26 @@ export namespace TestUtils {
ctx.drawImage(source, 0, 0);
return canvas;
}

/**
*
*/
export async function runOnWindows(ctx: () => Promise<any>): Promise<boolean> {
if (navigator.platform === 'Win32' || navigator.platform === 'Win64') {
await ctx();
return true;
}
return false;
}

/**
*
*/
export async function runOnLinux(ctx: () => Promise<any>): Promise<boolean> {
if (navigator.platform.includes('Linux')) {
await ctx();
return true;
}
return false;
}
}

0 comments on commit 00db29c

Please sign in to comment.