Есть ли способ объявить константу ссылки на метод во время компиляции?
Я работаю над интерпретатором script, который мне удалось получить в рабочем состоянии. Он имеет компилятор, который анализирует script и генерирует байт-код, и виртуальную машину, которая выполняет байт-код.
В основе интерпретатора лежит цикл с гигантским выражением case
, который выглядит примерно так:
case CurrentOpcode.Operation of
OP_1: DoOp1(CurrentOpcode);
OP_2: DoOp2(CurrentOpcode);
...
OP_N: DoOpN(CurrentOpcode);
end;
Профилирование говорит мне, что по какой-то причине мое выполнение script проводит значительное количество времени внутри этого оператора case
, что кажется мне странным, поэтому я ищу способ его оптимизации. Очевидное решение, так как все операционные функции в основном имеют одну и ту же подпись, заключается в создании массива указателей методов, индексированных по значению опкода Operation
. Но Operation
объявляется как enum, и было бы неплохо объявить это как массив const, чтобы, если я добавлю больше кодов операций в будущем, компилятор может напомнить мне об обновлении массива.
Так как указатель метода хранит состояние времени выполнения (ссылка Self
на объект, с которым он работает,), я не могу создать массив const указателей методов. (В любом случае это не будет хорошей идеей, так как вполне вероятно, что в итоге я буду работать одновременно с несколькими script.) Но методы - это просто синтаксический сахар. Что-то вроде:
procedure TMyObject.DoSomething(x, y: integer);
действительно означает:
procedure TMyObject_DoSomething(Self: TMyObject; x, y: integer);
Итак, я должен иметь возможность объявить тип указателя функции в последней форме и назначить его таким образом, а затем мне просто нужно явно передать Self
в качестве первого параметра при его вызове. Но компилятору это не нравится.
type TOpcodeProc = procedure (Self: TScriptVM; Opcode: TOpcode);
const OPCODE: TOpcodeProc = TScriptVM.DoOp1;
[DCC Error]: E2009 Incompatible types: 'regular procedure and method pointer'
Я пробовал разные варианты, чтобы попытаться собрать его, но все они дают ошибки. Есть ли способ получить это для компиляции?
Ответы
Ответ 1
Декларация:
const
OPCODE: array[TOperation] of Pointer = (
@TScriptVM.DoOp1,
@TScriptVM.DoOp2,
...
@TScriptVM.DoOpN
);
Вызов:
TOpcodeProc(OPCODE[CurrentOpcode.Operation])(Self, CurrentOpcode);
Более классный материал:
var
OpCodeProcs: array[TOperation] of TOpCodeProc absolute OPCODE;
Более удобный синтаксис для вызова метода:
OpCodeProcs[CurrentOpcode.Operation](Self, CurrentOpcode);
Хорошо, что из-за абсолюта константы компилятор не позволяет присваивать что-то переменной OpCodeProcs!
Ответ 2
Нет решения для постоянной части вашего вопроса, но вот как вы могли бы устранить case
:
type
TTestMethod = procedure(Instance: TObject; Param1, Param2: Integer);
TTest = class(TObject)
private
FMethods: array[0..1] of TTestMethod;
procedure InitMethods;
procedure CallMethod(ID: Integer; Param1, Param2: Integer);
protected
procedure TestMethod0(Param1, Param2: Integer);
procedure TestMethod1(Param1, Param2: Integer);
end;
procedure TTest.InitMethods;
begin
FMethods[0] := TTestMethod(@TTest.TestMethod0);
FMethods[1] := TTestMethod(@TTest.TestMethod1);
end;
procedure TTest.CallMethod(ID: Integer; Param1, Param2: Integer);
begin
FMethods[ID](Self, Param1, Param2);
end;