Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

エンティティの範囲外が描画されている時エラーを投げる処理を追加 #1043

Merged
merged 5 commits into from
Mar 8, 2022

Conversation

dera-
Copy link
Contributor

@dera- dera- commented Mar 2, 2022

概要

下記issueへの対応。Safariのみへの対応は難しいのでコンテンツのテスト環境であるakashic-cli-serveにてエンティティの範囲外が描画されている時エラーを投げる対応を行いました。

やったこと

  • akashic-cli-serve上で実行されるコンテンツのPrimarySurfaceやSurfaceにエンティティの範囲外が描画範囲として指定されていた時エラーを投げる処理を差し込んだ

@dera- dera- requested review from kamakiri01 and xnv March 2, 2022 10:17
@@ -5,8 +5,10 @@ import { ServeGameContent } from "./ServeGameContent";
import { generateTestbedScriptAsset } from "./TestbedScriptAsset";

export interface Platform {
getPrimarySurface: () => any; // 戻り値は `g.Surface` (コンパイル時に g がないので any で妥協)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any で妥協するより最低限必要なものだけ定義しませんか。(ScriptAsset と違って丸ごと差し替えてるわけでもないですし) たぶん、このぐらいの話ではないかと思います。

interface RendererLike {
  drawImage(surface: SurfaceLike, srcX: number, /* 中略 */ destY: number): void;
}
interface SurfaceLike {
  width: number;
  height: number;
  renderer(): RendererLike;
}

オーバーライドする箇所で as any が必要かもしれませんが、その直前までは型つきで書けると思います。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます、確かにここで利用する分だけ型定義したほうがよさそうですね。そのように型を定義しておきます

}
const renderer = originalRenderer.apply(this);
const originalDrawImage = renderer.drawImage;
renderer.drawImage = function (surface: any, offsetX: number, offsetY: number, width: number, height: number) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

後から見た時に混乱しそうなので、引数は使わないにしても元関数と同じにしておきたいです。たぶん destX, destY 的な値がありますよね。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destXやdestYは不要だったので省略してましたが、シグニチャも元関数に合わせておくべきでした。。利用しない引数については先頭に_を付けておくことで対応しようと思います

const originalDrawImage = renderer.drawImage;
renderer.drawImage = function (surface: any, offsetX: number, offsetY: number, width: number, height: number) {
if (offsetX < 0 || offsetX + width > surface.width || offsetY < 0 || offsetY + height > surface.height) {
throw new Error(`Please draw with following range. x: 0-${surface.width}, y: 0-${surface.height}.`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

「(バグではないが) プラットフォーム固有の描画トラブルを防ぐために akashic serve がエラーにしている」という情報をメッセージに含めたいです。こんな感じですかねえ。

drawImage(): out of bounds. The source rectangle bleeds out the source surface (${surface.width}x${surface.height}). This is not a bug but intentionally prohibited by akashic serve to prevent platform-specific rendering trouble.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。どの箇所でエラーを出しているのか、バグではないがプラットフォーム固有の描画トラブルを防ぐための対応という説明もいれておくべきでしたね。そのように修正しておきます

const originalDrawImage = renderer.drawImage;
renderer.drawImage = function (surface: any, offsetX: number, offsetY: number, width: number, height: number) {
if (offsetX < 0 || offsetX + width > surface.width || offsetY < 0 || offsetY + height > surface.height) {
throw new Error(`Please draw with following range. x: 0-${surface.width}, y: 0-${surface.height}.`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

せっかく issue 契機の対応なので、元の issue へのリンクを // ref. https://〜 とコメントで残しておきませんか。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

了解しました。issueへのリンクをコメントとして残しておこうと思います

throw new Error(`Please draw with following range. x: 0-${surface.width}, y: 0-${surface.height}.`);
}
if (width <= 0 || height <= 0) {
throw new Error(`Please set width and height to value higher than 0.`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここも。こんな感じとか。 drawImage(): nothing to draw. Either width or height is less than or equal to zero. This is not a bug but intentionally prohibited by akashic serve to prevent platform-specific rendering trouble.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。こちらもそのようにエラー文言を修正しておきます

Comment on lines 165 to 166
// 描画元の範囲が指定されたwidth,heightを超える値もしくは0以下が与えられた場合Safariでのみ描画されないという問題が発生するので、g.Surface#renderer()でエラーを投げる処理を差し込む
// funcにはg.Surfaceを返す関数が渡されることを想定している。本来は型で縛るべきだがコンパイル時に g がないのでFunction型で妥協。
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createMeddlingWrappedSurfaceFactory<T extends (...args: any[]) => SurfaceLike>(func: T): T とすれば「妥協」のコメントはなくせそうです。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。この関数にそのように型を付けおこうと思います

@@ -160,6 +162,38 @@ export class GameViewManager {
() => options.g.ExceptionFactory.createAssetLoadError("can not load script: " + assetPath)
);
};
// 描画元の範囲が指定されたwidth,heightを超える値もしくは0以下が与えられた場合Safariでのみ描画されないという問題が発生するので、g.Surface#renderer()でエラーを投げる処理を差し込む
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 「エラーを投げる処理」が差し込まれてるのは drawImage() かと思います。
  • 詳細な条件はコードを見ればただちに分かることなので、ここでは「一部のエッジケースで Safari のみ描画結果が異なることがあるので」ぐらいでよいかと思います。
  • またエラーを投げる「意図」もコメントしておきたいです。「ゲーム開発者が開発中に気づけるように、エラーを投げる処理を差し込む」ですよね。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます、なぜエラーを投げるのかの説明が抜け落ちていましたのでこちらの追記も含めて修正しておきます。

if (rendererCache) {
return rendererCache;
}
const renderer = originalRenderer.apply(this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

外側の rendererCacherenderer という名前にしてしまえば、ここで const しなくてもそのまま代入するだけでよいのでは。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

仰る通りでした、そのように修正しておきます

const originalRenderer = surface.renderer;
// drawImageメソッドの中で元のdrawImageメソッドを利用する実装のため、rendererをキャッシュしないとrenderer呼び出しの度にバリデーション処理が増えてしまう
// 本来なら前回のrendererの内容と比較してdiffがあるかを判定する対応にすべきだが、rendererの内容は不変なので単純にrendererをキャッシュするだけの対応としている
let rendererCache: any = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

別コメントのとおり、ほとんどの any はなくせると思います。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確かに上記でご提案していただいたRendererLikeが利用できそうですね。そのように型付けを修正しようと思います

Comment on lines 171 to 172
// drawImageメソッドの中で元のdrawImageメソッドを利用する実装のため、rendererをキャッシュしないとrenderer呼び出しの度にバリデーション処理が増えてしまう
// 本来なら前回のrendererの内容と比較してdiffがあるかを判定する対応にすべきだが、rendererの内容は不変なので単純にrendererをキャッシュするだけの対応としている
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

「drawImageメソッドの中で元のdrawImageメソッドを利用する実装のため」に主語がない上にここからちょっと離れた箇所の話だったり、「バリデーション処理」という言葉はここでしか登場しなかったりで、初見だと意図がつかみにくそうです。

前提から全部書くなら

  • surface.renderer() の戻り値は現実的に固定であり、かつ
  • surface.renderer() はコンテンツから描画のたびに呼び出されるため、
  • 単純に renderer() をラップして drawImage() を差し替えると描画のたびに差し替えが発生してしまう
  • ここでの差し替え処理は、「引数のバリデーションを行なって元の関数を呼び出す」ものなので、多重に適用するとバリデーションが多重になってしまう

となると思いますが、簡潔に「surface.renderer() の戻り値は現実的に固定なので、ここでの renderer() 上書きは一度しか適用しない」ぐらいの内容でいいと思います。 if (rendererCache) return rendererCache; のところに書くのでどうでしょうか。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、3,4つ目については言及してましたが大分説明が抜け落ちていたために分かりにくくなっていたのとどちらかというとディティールの部分なのでそれよりは1,2つ目について言及すべきでしたね。。そのようにコメントを修正しておこうと思います

Copy link
Member

@xnv xnv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメント加えていますが approved.

@@ -4,9 +4,29 @@ import { NullScriptAssetV3 } from "./AssetV3";
import { ServeGameContent } from "./ServeGameContent";
import { generateTestbedScriptAsset } from "./TestbedScriptAsset";

export interface RendererLike {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すいませんこの名前私のコメント由来かと思いますが、 Platform に合わせるなら Renderer が妥当ですね……。

あと、 (Platform も含めて) 可能なら export を外したいです。これらは「元の型定義を参照できない値」を扱うにあたって、"このファイル内で" しょうもない typo などが入り込まないようにするという意味合いが強いためです。「このファイル内の事故防止のための、このファイルが扱うプロパティだけ記述した最低限の定義」が export されるのは違和感があります。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

了解しました。「元の型定義を参照できない値」を扱うためだけに型定義しているもの(PlatForm, RendererLike, SurfaceLike)からexportを外す対応をすると同時に、RendererLikeとSurfaceLikeのLikeを外す対応も行います

@dera- dera- merged commit 89576e5 into master Mar 8, 2022
@dera- dera- deleted the throw-error-about-drawing-out-of-range branch March 8, 2022 04:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants