refac: bootstrap()

- 각 클래스가 WeakRef로 상호 참조한다면, construct 시점엔 불가
- bootstrap() 함수로 이후 참조
This commit is contained in:
2024-03-14 16:26:40 +00:00
parent 3e4129c3f1
commit b4b00e1983
14 changed files with 335 additions and 59 deletions
+4
View File
@@ -16,6 +16,10 @@ export class City extends LazyEntityUpdater<ICityEntity>{
this._raw = raw;
}
public bootstrap(){
//do nothing;
}
get id(): CityID {
return this._raw.id;
}
@@ -1,3 +1,5 @@
import type { IntYear, IntYearMonth } from "@/defs.js";
export type UnitedGameState = 'onGame' | 'united' | 'onEvent' | 'endEvent';
export interface IGameEnvEntity {
@@ -10,8 +12,9 @@ export interface IGameEnvEntity {
gameStartYear: number;
gameStartMonth: number;
year: number;
month: number;
year: IntYear;
month: IntYearMonth;
yearMonth: IntYearMonth;
secondsPerTurn: number;
@@ -1,4 +1,4 @@
import type { CityID, DiplomaticPermission, FractionalRange, GeneralID, GeneralName, IntMonth, IntYear, ItemID, ItemKeyType, NationID, NpcType, OfficerLevel, PersonalityType, SpecialityDomesticType, SpecialityWarType, SquadID, URILike, UserID, UserName } from "../defs.js";
import type { CityID, DiplomaticPermission, FractionalRange, GeneralID, GeneralName, IntMonth, IntYear, IntYearMonth, ItemID, ItemKeyType, NationID, NpcType, OfficerLevel, PersonalityType, SpecialityDomesticType, SpecialityWarType, SquadID, URILike, UserID, UserName } from "../defs.js";
export interface IGeneralEntity {
id: GeneralID;
@@ -49,7 +49,7 @@ export interface IGeneralEntity {
age: IntYear;
startAge: IntYear;
nationJoinedRelYear: IntYear;
nationJoinedYearMonth: IntYearMonth;
betray: number;
@@ -69,4 +69,9 @@ export interface IGeneralEntity {
bornyear: number;
deadyear: number;
}
aux: {
lastBattleDate?: Date;
lastBattleYearMonth?: IntYearMonth;
}
}
@@ -51,6 +51,9 @@ export interface INationEntity{
availableWarSettingCnt?: number;
prevIncomeGold: number;
prevIncomeRice: number;
lastBattleDate?: Date;
lastBattleYearMonth?: IntYearMonth;
}
//TODO: 타입 정의
+6
View File
@@ -0,0 +1,6 @@
export class LogicError extends Error {
constructor(message?: string) {
super(message);
this.name = 'LogicError';
}
}
+3
View File
@@ -17,4 +17,7 @@ export class GameEnv extends LazyEntityUpdater<IGameEnvEntity>{
this._raw = raw;
}
public bootstrap(){
//do nothing;
}
}
+36 -10
View File
@@ -2,7 +2,7 @@ import { InvalidArgument } from "@sammo/util";
import { City } from "./City.js";
import type { IGeneralEntity } from "./Entity/GeneralEntity.js";
import type { IResourceController } from "./IResourceController.js";
import { DiplomaticPermission, type CityID, type GeneralID, type GeneralName, type NationID, OfficerLevel, type SquadID } from "./defs.js";
import { DiplomaticPermission, type CityID, type GeneralID, type GeneralName, type NationID, OfficerLevel, type SquadID, NpcType, StaffLevel, CityOfficerLevel } from "./defs.js";
import { must } from "@sammo/util";
import { Nation } from "./Nation.js";
import { Squad } from "./Squad.js";
@@ -17,11 +17,11 @@ export class General extends LazyEntityUpdater<IGeneralEntity> {
protected readonly _entityType = "General";
protected readonly _raw: IGeneralEntity;
protected _city: WeakRef<City>;
protected _squad: WeakRef<Squad> | undefined;
protected _nation: WeakRef<Nation>;
protected _city!: WeakRef<City>;
protected _squad!: WeakRef<Squad> | undefined;
protected _nation!: WeakRef<Nation>;
protected readonly iActionHandlers: Map<IActionKey, IAction>;
protected readonly iActionHandlers = new Map<IActionKey, IAction>;
constructor(
raw: IGeneralEntity,
@@ -29,6 +29,11 @@ export class General extends LazyEntityUpdater<IGeneralEntity> {
){
super();
this._raw = raw;
}
public bootstrap(){
const rsc = this.rsc;
const raw = this._raw;
this._city = new WeakRef(must(rsc.city(raw.cityID)));
this._nation = new WeakRef(must(rsc.nation(raw.nationID)));
@@ -36,8 +41,7 @@ export class General extends LazyEntityUpdater<IGeneralEntity> {
this._squad = new WeakRef(must(rsc.squad(raw.squadID, raw.nationID)));
}
//TODO: iAction 처리해야함
this.iActionHandlers = new Map();
//TODO: iActionHandlers 초기화
}
public get id(): GeneralID {
@@ -60,6 +64,28 @@ export class General extends LazyEntityUpdater<IGeneralEntity> {
return must(this._nation.deref());
}
public get isUser(): boolean {
return this._raw.npcType <= NpcType.user_borrowed_npc;
}
public get staffLevel(): StaffLevel | undefined {
if(this._raw.officerLevel < OfficerLevel.staffIntel3){
return undefined;
}
return this._raw.officerLevel as number as StaffLevel;
}
public get cityOfficerLevel(): CityOfficerLevel | undefined {
if(this._raw.officerLevel >= OfficerLevel.staffIntel3){
return undefined;
}
if(this._raw.officerLevel < OfficerLevel.cityLeadership){
return undefined;
}
return this._raw.officerLevel as number as CityOfficerLevel;
}
public changeCity(city: CityID | City){
const cityObj: City = (()=>{
if(city instanceof City){
@@ -86,7 +112,7 @@ export class General extends LazyEntityUpdater<IGeneralEntity> {
const squad = must(this._squad.deref());
if(squad.leader.id === this._raw.id){
squad.destructSquad(false);
squad.destroySquad(false);
}
else{
squad.members.delete(this._raw.id);
@@ -142,12 +168,12 @@ export class General extends LazyEntityUpdater<IGeneralEntity> {
else{
raw.officerLevel = OfficerLevel.normal;
}
raw.nationJoinedRelYear = 0;
raw.nationJoinedYearMonth = this.rsc.gameEnv.raw.yearMonth;
});
}
public updateTurnTime(){
const secondsPerTurn = this.rsc.gameEnv().raw.secondsPerTurn;
const secondsPerTurn = this.rsc.gameEnv.raw.secondsPerTurn;
this.forceUpdate((raw) => {
raw.turnTime = addSeconds(raw.turnTime, secondsPerTurn);
+5 -14
View File
@@ -22,21 +22,12 @@ export interface IResourceController {
popGeneralTurnTimeQueue(): General | undefined;
insertGeneralTurnTimeQueue(general: General, maybeTail: boolean): void;
gameEnv(): GameEnv;
readonly gameEnv: GameEnv;
allNations(): Map<NationID, Nation>;
allCities(): Map<CityID, City>;
allCitiesByNation(): Map<NationID, Map<CityID, City>>;
allSquads(): Map<NationID, Map<SquadID, Squad>>;
allGenerals(): Map<GeneralID, General>;
generalByNation(id: NationID): Map<GeneralID, General>;
generalByCity(id: CityID, nationID?: NationID): Map<GeneralID, General>;
generalBySquad(id: SquadID): Map<GeneralID, General>;
cityByNation(id: NationID): Map<CityID, City>;
squadByNation(id: NationID): Map<SquadID, Squad>;
readonly nationList: Map<NationID, Nation>;
readonly cityList: Map<CityID, City>;
readonly squadList: Map<SquadID, Squad>;
readonly generalList: Map<GeneralID, General>;
//----Insert, Update, Delete
@@ -9,6 +9,8 @@ export abstract class LazyEntityUpdater<Entity extends ValidEntity>{
protected abstract readonly rsc: IResourceController;
public abstract bootstrap(): void;
public get raw(): Readonly<Entity> {
return this._raw;
}
@@ -0,0 +1,20 @@
import type { General } from "@/General.js";
import { NotYetImplemented } from "@sammo/util";
const battleMonthThreshold = 12;
export function categorizeGeneral(general: General): 'war' | 'domestic' {
const nationLastBattle = general.nation.raw.aux.lastBattleYearMonth;
const lastBattle = general.raw.aux.lastBattleYearMonth;
if(lastBattle !== undefined && nationLastBattle !== undefined){
if(lastBattle >= nationLastBattle - battleMonthThreshold){
return 'war';
}
}
throw new NotYetImplemented();
return 'domestic';
}
+142 -2
View File
@@ -1,21 +1,101 @@
import { LazyEntityUpdater } from "./LazyEntityUpdater.js";
import type { INationEntity } from "./Entity/NationEntity.js";
import type { IResourceController } from "./IResourceController.js";
import type { NationID, NationName } from "./defs.js";
import { StaffLevel, type CityID, type GeneralID, type NationID, type NationName, type SquadID } from "./defs.js";
import type { City } from "./City.js";
import { General } from "./General.js";
import { must } from "@sammo/util";
import type { Squad } from "./Squad.js";
import { LogicError } from "./Errors.js";
import { categorizeGeneral } from "./LogicFunc/categorizeGeneral.js";
export class Nation extends LazyEntityUpdater<INationEntity> {
protected readonly _indirectNames: (keyof INationEntity)[] = ["id"];
protected readonly _entityType = "Nation";
protected readonly _raw: INationEntity;
protected readonly categorizedNPCGeneralList = {
war: new Map<GeneralID, WeakRef<General>>(),
domestic: new Map<GeneralID, WeakRef<General>>()
};
protected readonly categorizedUserGeneralList = {
war: new Map<GeneralID, WeakRef<General>>(),
domestic: new Map<GeneralID, WeakRef<General>>()
};
protected readonly cityList = new Map<CityID, WeakRef<City>>;
protected readonly generalList = new Map<GeneralID, WeakRef<General>>;
protected readonly squadList = new Map<SquadID, WeakRef<Squad>>;
protected readonly npcGeneralList = new Map<GeneralID, WeakRef<General>>();
protected readonly userGeneralList = new Map<GeneralID, WeakRef<General>>();
protected readonly warNPCGeneralList = new Map<GeneralID, WeakRef<General>>();
protected readonly warUserGeneralList = new Map<GeneralID, WeakRef<General>>();
protected readonly domesticNPCGeneralList = new Map<GeneralID, WeakRef<General>>();
protected readonly domesticUserGeneralList = new Map<GeneralID, WeakRef<General>>();
protected readonly staffList = new Map<StaffLevel, WeakRef<General>>();
constructor(
raw: INationEntity,
protected readonly rsc: IResourceController
){
) {
super();
this._raw = raw;
}
bootstrap() {
const rsc = this.rsc;
const raw = this._raw;
//raw값만 읽을것
for (const city of rsc.cityList.values()) {
if (city.raw.nationID !== raw.id) {
continue;
}
this.cityList.set(city.id, new WeakRef(city));
}
for (const squad of rsc.squadList.values()) {
if (squad.raw.nationID !== raw.id) {
continue;
}
this.squadList.set(squad.id, new WeakRef(squad));
}
for (const general of rsc.generalList.values()) {
if (general.raw.nationID !== raw.id) {
continue;
}
this.generalList.set(general.id, new WeakRef(general));
if (general.isUser) {
this.userGeneralList.set(general.id, new WeakRef(general));
if (categorizeGeneral(general) === "war") {
this.warUserGeneralList.set(general.id, new WeakRef(general));
}
else {
this.domesticUserGeneralList.set(general.id, new WeakRef(general));
}
} else {
this.npcGeneralList.set(general.id, new WeakRef(general));
if (categorizeGeneral(general) === "war") {
this.warNPCGeneralList.set(general.id, new WeakRef(general));
}
else {
this.domesticNPCGeneralList.set(general.id, new WeakRef(general));
}
}
const staffLevel = general.staffLevel;
if (staffLevel !== undefined) {
this.staffList.set(staffLevel, new WeakRef(general));
}
}
}
public get id(): NationID {
return this._raw.id;
}
@@ -23,4 +103,64 @@ export class Nation extends LazyEntityUpdater<INationEntity> {
public get name(): NationName {
return this._raw.name;
}
public _kickGeneral(general: General | GeneralID) {
//국가에서 장수가 빠져나가는 경우, class내 변수에 대한 처리.
if (!(general instanceof General)) {
general = must(this.generalList.get(general)?.deref());
}
const generalID = general.id;
this.generalList.delete(generalID);
if (general.isUser) {
this.userGeneralList.delete(generalID);
this.warUserGeneralList.delete(generalID);
this.domesticUserGeneralList.delete(generalID);
} else {
this.npcGeneralList.delete(generalID);
this.warNPCGeneralList.delete(generalID);
this.domesticNPCGeneralList.delete(generalID);
}
const staffLevel = general.staffLevel;
if (staffLevel !== undefined) {
this.staffList.delete(staffLevel);
if (staffLevel === StaffLevel.lord) {
throw new LogicError(`Lord is kicked: nationID=${this.id}`);
}
}
if (general.squad) {
general.quitSquad();
}
}
public notifyDestroySquad(squad: Squad) {
this.squadList.delete(squad.id);
}
public _joinGeneral(general: General | GeneralID) {
//국가에 장수가 들어오는 경우, class내 변수에 대한 처리.
if (!(general instanceof General)) {
general = must(this.rsc.general(general));
}
const generalID = general.id;
if (general.isUser) {
this.userGeneralList.set(generalID, new WeakRef(general));
} else {
this.npcGeneralList.set(generalID, new WeakRef(general));
}
const staffLevel = general.staffLevel;
if (staffLevel !== undefined) {
this.staffList.set(staffLevel, new WeakRef(general));
}
}
}
+17 -9
View File
@@ -9,21 +9,27 @@ export class Squad extends LazyEntityUpdater<ISquadEntity> {
protected readonly _indirectNames: (keyof ISquadEntity)[] = ["id"];
protected readonly _entityType = "Squad";
protected readonly _raw: ISquadEntity;
private _leader: WeakRef<General>;
private _members: Map<GeneralID, WeakRef<General>>;
private _leader!: WeakRef<General>;
private _members!: Map<GeneralID, WeakRef<General>>;
constructor(
raw: ISquadEntity,
protected rsc: IResourceController
protected rsc: IResourceController,
private _rawMembers: GeneralID[]
){
super();
this._raw = raw;
}
const members = rsc.generalBySquad(raw.id);
const leaderID: GeneralID = raw.id as GeneralID;
this._leader = new WeakRef(must(members.get(leaderID)));
members.delete(leaderID);
this._members = new Map([...members].map(([k,v])=>[k, new WeakRef(v)]));
public bootstrap(){
this._members = new Map(this._rawMembers.map((id)=>{
return [id, new WeakRef(must(this.rsc.general(id)))];
}));
const leaderID = this._raw.id as GeneralID;
this._leader = must(this._members.get(leaderID));
this._members.delete(leaderID);
}
public get id(){
@@ -38,7 +44,7 @@ export class Squad extends LazyEntityUpdater<ISquadEntity> {
return this._members;
}
public destructSquad(withLeader = true){
public destroySquad(withLeader = true){
this._members.forEach((member)=>{
const general = must(member.deref());
general.quitSquad();
@@ -47,6 +53,8 @@ export class Squad extends LazyEntityUpdater<ISquadEntity> {
this.leader.quitSquad();
}
this.leader.nation.notifyDestroySquad(this);
this.rsc.reserveDelete(this);
}
+2 -2
View File
@@ -81,7 +81,7 @@ export class GameEngine {
upcomingGeneral.updateTurnTime();
this.rsc.insertGeneralTurnTimeQueue(upcomingGeneral, true);
this.rsc.gameEnv().update((env) => {
this.rsc.gameEnv.update((env) => {
env.lastExecuted = generalTurnTime;
})
processedGeneralCount++;
@@ -89,7 +89,7 @@ export class GameEngine {
if (processedGeneralCount > 0) {
await this.prevSaveWaiter;
const lastExecuted = this.rsc.gameEnv().raw.lastExecuted;
const lastExecuted = this.rsc.gameEnv.raw.lastExecuted;
this.loggerEngine.flushAll();
this.prevSaveWaiter = this.rsc.saveAll().then(() => {
this.postStatus({
+83 -18
View File
@@ -1,19 +1,20 @@
import type { IResourceController } from "@sammo/game_logic";
import type { City } from "@sammo/game_logic/src/City.js";
import { City } from "@sammo/game_logic/src/City.js";
import type { CityID, GeneralID, NationID, SquadID } from "@sammo/game_logic/src/defs.js";
import type { IGeneralEntity } from "@sammo/game_logic/src/Entity/GeneralEntity.js";
import type { ICityEntity, ValidEntityType } from "@sammo/game_logic/src/Entity/index.js";
import type { INationEntity } from "@sammo/game_logic/src/Entity/NationEntity.js";
import type { ReservedTurn } from "@sammo/game_logic/src/defs.js";
import type { ISquadEntity } from "@sammo/game_logic/src/Entity/SquadEntity.js";
import type { GameEnv } from "@sammo/game_logic/src/GameEnv.js";
import { GameEnv } from "@sammo/game_logic/src/GameEnv.js";
import { General } from "@sammo/game_logic/src/General.js";
import type { LazyEntityUpdater } from "@sammo/game_logic/src/LazyEntityUpdater.js";
import type { Nation } from "@sammo/game_logic/src/Nation.js";
import type { Squad } from "@sammo/game_logic/src/Squad.js";
import { insertItemAtArrayLike, NotYetImplemented } from "@sammo/util";
import { Nation } from "@sammo/game_logic/src/Nation.js";
import { Squad } from "@sammo/game_logic/src/Squad.js";
import { insertItemAtArrayLike, must, NotYetImplemented } from "@sammo/util";
import Denque from "denque";
import type { Mongoose } from "mongoose";
import type { IGameEnvEntity } from "@sammo/game_logic/src/Entity/GameEnvEntity.js";
type ChangeType = "insert" | "update" | "delete";
@@ -22,13 +23,17 @@ export class ResourceController implements IResourceController{
constructor(
private readonly db: Mongoose
) {
}
private _nation: Map<number, Nation> = new Map();
private _city: Map<number, City> = new Map();
private _squad: Map<number, Squad> = new Map();
private _general: Map<number, General> = new Map();
private _gameEnv: GameEnv = new GameEnv({} as IGameEnvEntity, this);
public get gameEnv(): GameEnv {
return this._gameEnv;
}
public readonly nationList: Map<NationID, Nation> = new Map();
public readonly cityList: Map<CityID, City> = new Map();
public readonly squadList: Map<SquadID, Squad> = new Map();
public readonly generalList: Map<GeneralID, General> = new Map();
//내부적으로 turnTime asc, id asc로 동작하는 PriorityQueue
private _generalByTurnTime = new Denque<General>();
@@ -45,19 +50,26 @@ export class ResourceController implements IResourceController{
} as const satisfies Record<ValidEntityType, Map<number, [ChangeType, LazyEntityUpdater<any>]>>;
nation(id: NationID): Nation | undefined {
throw new NotYetImplemented();
return this.nationList.get(id);
}
city(id: CityID): City | undefined {
throw new NotYetImplemented();
return this.cityList.get(id);
}
squad(id: SquadID, nation: NationID): Squad | undefined {
throw new NotYetImplemented();
const squad = this.squadList.get(id);
if(!squad){
return undefined;
}
if(squad.raw.nationID !== nation){
return undefined;
}
return squad;
}
general(id: GeneralID): General | undefined {
throw new NotYetImplemented();
return this.generalList.get(id);
}
peekGeneralTurnTimeQueue(): General | undefined {
@@ -72,10 +84,6 @@ export class ResourceController implements IResourceController{
insertItemAtArrayLike(this._generalByTurnTime, general, (a, b)=>a.compareTurnTime(b), maybeTail);
}
gameEnv(): GameEnv {
throw new NotYetImplemented();
}
allNations(): Map<NationID, Nation> {
throw new NotYetImplemented();
}
@@ -159,6 +167,63 @@ export class ResourceController implements IResourceController{
}
resetAndFill(): Promise<void> {
this._generalByTurnTime.clear();
this.nationList.clear();
this.cityList.clear();
this.squadList.clear();
this.generalList.clear();
//MongoDB로부터 데이터를 가져와서 채워넣는다.
const rawGameEnv = {} as IGameEnvEntity;
//TODO: Implement!
const rawNationList = new Map<NationID, INationEntity>();
const rawCityList = new Map<CityID, ICityEntity>();
const rawSquadList = new Map<SquadID, ISquadEntity>();
const rawGeneralList = new Map<GeneralID, IGeneralEntity>();
const rawGeneralListBySquad = new Map<SquadID, GeneralID[]>();
for(const rawSquad of rawSquadList.values()){
rawGeneralListBySquad.set(rawSquad.id, []);
}
for(const rawGeneral of rawGeneralList.values()){
if(!rawGeneral.squadID){
continue;
}
const list = must(rawGeneralListBySquad.get(rawGeneral.squadID));
list.push(rawGeneral.id);
}
this._gameEnv = new GameEnv(rawGameEnv, this);
this._gameEnv.bootstrap();
for(const rawNation of rawNationList.values()){
const nation = new Nation(rawNation, this);
this.nationList.set(nation.id, nation);
}
for(const rawCity of rawCityList.values()){
const city = new City(rawCity, this);
this.cityList.set(city.id, city);
}
for(const rawSquad of rawSquadList.values()){
const squad = new Squad(rawSquad, this, must(rawGeneralListBySquad.get(rawSquad.id)));
this.squadList.set(squad.id, squad);
}
for(const rawGeneral of rawGeneralList.values()){
const general = new General(rawGeneral, this);
this.generalList.set(general.id, general);
}
//lazyInit()을 호출한다.
this.nationList.forEach(nation=>nation.bootstrap());
this.cityList.forEach(city=>city.bootstrap());
this.squadList.forEach(squad=>squad.bootstrap());
//iAction때문에 general의 lazyInit이 가장 마지막
this.generalList.forEach(general=>general.bootstrap());
throw new NotYetImplemented();
}