euddraft icon indicating copy to clipboard operation
euddraft copied to clipboard

Make `StringBuffer` to `object`.

Open armoha opened this issue 1 year ago • 2 comments

We need to expand EUDStruct to fully port current behavior of StringBuffer and prevent performance regression.

Highly related to armoha/euddraft#58

Background

constructor and constructor_static

object Obj {};

const staticObj = Obj();  // calls constructor_static and maybe constructor too
const dynamicObj = Obj.alloc();  // calls constructor

var objVar = globalStaticObj;
const globalObjFromCast = Obj.cast(objVar);  // does not call any constructor

function foo(obj: Obj) {}
function afterTriggerExec() {
    foo(objVar);  // does not call any constructor
}

Default implementation of constructor_static calls constructor too, but user is free to override it to make specialized and separate code for static declaration:

class EUDStruct(ut.ExprProxy, metaclass=_EUDStruct_Metaclass):

    # Constructor & Destructor of classes
    def constructor(self):
        """Constructor for individual structures.

        Default constructor accepts no arguments, but derived classes may
        accept additional arguments.

        This function is called when
            - Argument is allocated from pool   (self.isPooled = True)
            - Argument is generated             (self.isPooled = False)

        You may choose to either allocate member from pool or just allocate
        members statically via self.isPooled.
        """
        pass

    def constructor_static(self, *args, **kwargs):
        """Specialized constructor for static variables.

        Static variable may not require allocation for member variables.
        Function may specialize their behavior by overriding this function"""
        self.constructor(*args, **kwargs)

    def destructor(self):
        """Destructor for individual structures.

        Destructor accepts no arguments. Destructor is called when
            - Manually called. (Ex: stack variable)
            - free() is called for object
        """
        pass

Motivation

We want to pass StringBuffer to function but we don't want to make StringBuffer too slow.

StringBuffer.StringIndex, .epd and .capacity should be static

  • .StringIndex : map string id StringBuffer uses
  • .epd : starting address of string content
  • .capacity : every string has fixed capacity and can't reallocate (Changing starting offset of string is not supported in StarCraft: Remastered. Starting addresses of every strings are fixed.)

It is breaking change to make var field to const or static field. It is okay to change static field to const or var field.

Field mutability

var field

  • Usual field, free to mutate.

const field

  • Similar to const, the field can't be reassigned once it's initialized.
  • example: CUnit fields of UnitGroup and EUDBag (once it's group.add(cunit)ed, you can't further mutate.)

static field

  • static field access always happens in compile time when object is known in compile time. No runtime cost.
  • Implementor must initialize every static fields in constructor_static.
  • Can't store EUDVariable in static field.
  • Object.alloc is not allowed when Object has any static field
# eudplib syntax to declare fields
class Obj(EUDStruct):
    _fields_ = [
        "varfield",  # var field without type
        ("untyped_var_field", None),  # equivalent code
        ("typed_var_field", EUDArray),
        # new syntax below
        ("verbose_var_field", None, "var"),
        ("const_field", None, "const"),
        ("static_field", TrgUnit, "static"),
    ]

How to implement

EUDStruct.__init__

image 위에는 객체.cast(인스턴스) 하는거 (_from이 있을 때) 아래는 객체(인자) 정적 선언하는거 (else:)

  • 목표: constructor_static에서 static필드를 setattr한 걸 모아서super(ExprProxy).__init__에 사용하는 EUDVArray의 초기값으로 넣기

(maybe we need to use Forward()?)

._initialized

  • ._initialized == True인스턴스.필드 = 값;을 하면 VArray[필드인덱스] = 값 코드가 된다 (없는 필드를 대입하면 컴파일 오류)
  • ._initialized 가 없으면 일반적인 파이썬 코드처럼 self.__dict__[필드] = 값

image

Q. allow mutations only within constructor_static?

object ObjectWithStaticField {
    static field_static;
    function constructor_static() {
        this.field_static = $U("Fenix (Zealot)");
        // Q. constructor_static 안에서만 변경을 허용할지?
        this.field_static *= 2;  // 헷갈리니까 막는게 나을 거 같음
    }
};

Q. optimize global object initialization?

object Obj {
    var v;
    const c;
    static s;
    function constructor_static() {
        this.v = 1;
        this.c = 2;
        this.s = 3;
        this.constructor();  // optional
    }
};
const obj_global = Obj();
function beforeTriggerExec() {
    const obj_local = Obj();
}
  • obj_global은 1, 2, 3 대입하는 트리거를 만드는 대신에 EUDVArray의 초기값을 [1, 2, 3]으로 넣으면 좋을 듯
  • obj_localvar 필드인 .aconst 필드인 .b에 1, 2 대입하는 트리거를 꼭 만들어야함

Q. change .constructor, .constructor_static and .destructor to static methods in epScript? (No @EUDMethod)

This will be breaking change that every constructors and destructors will duplicate codes, and users need to migrate to make separate methods and calling them on ctors and dtors to get previous behaviors.

Q. do we need static if?

// we can write this in eudplib but can't in epScript
object Obj {
    var a, b;
    static s;
    function constructor() {
        if (this.isPooled) {
            // this object is dynamically allocated with Obj.alloc();
        } else {
            // this object is statically allocated with Obj(arg);
        }
    }
    function constructor_static(arg) {
        if (py_isinstance(arg, py_str)) {
            // arg is str
        } else if (py_isinstance(arg, py_int)) {
            // arg is int
        }
        this.constructor();  // optional
    }
};

armoha avatar May 22 '23 04:05 armoha

Actually I started to lose interest in making static field as public API. It complicates the language but does not hold much weight. e.g. ancient version of Rust had immutable struct field but later dropped it.

armoha avatar May 22 '23 19:05 armoha

It's mostly solved in euddraft 0.9.10.2.

armoha avatar Dec 29 '23 09:12 armoha