1159 lines
61 KiB
TypeScript
1159 lines
61 KiB
TypeScript
import * as UE from 'ue'
|
|
import {$ref, $unref, $set} from 'puerts';
|
|
import * as ts from "typescript";
|
|
import * as tsi from "./TypeScriptInternal"
|
|
import * as uemeta from "./UEMeta"
|
|
import * as cpp from 'cpp'
|
|
|
|
function getCustomSystem(): ts.System {
|
|
const customSystem: ts.System = {
|
|
args: [],
|
|
newLine: '\n',
|
|
useCaseSensitiveFileNames: true,
|
|
write,
|
|
readFile,
|
|
writeFile,
|
|
resolvePath: tsi.resolvePath,
|
|
fileExists,
|
|
directoryExists: tsi.directoryExists,
|
|
createDirectory: tsi.createDirectory,
|
|
getExecutingFilePath,
|
|
getCurrentDirectory,
|
|
getDirectories,
|
|
readDirectory,
|
|
exit,
|
|
}
|
|
|
|
function fileExists(path: string): boolean {
|
|
let res = UE.FileSystemOperation.FileExists(path);
|
|
//console.log(`${path} exists? ${res}`);
|
|
return res;
|
|
}
|
|
|
|
function write(s: string): void {
|
|
console.log(s);
|
|
}
|
|
|
|
function readFile(path: string, encoding?: string): string | undefined {
|
|
let data = $ref<string>(undefined);
|
|
const res = UE.FileSystemOperation.ReadFile(path, data);
|
|
if (res) {
|
|
return $unref(data);
|
|
} else {
|
|
console.warn("readFile: read file fail! path=" + path + ", stack:" + new Error().stack);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function writeFile(path: string, data: string, writeByteOrderMark?: boolean): void {
|
|
throw new Error("forbiden!")
|
|
}
|
|
|
|
/*function resolvePath(path: string): string {
|
|
throw new Error("resolvePath no supported!");
|
|
}*/
|
|
|
|
function readDirectory(path: string, extensions?: ReadonlyArray<string>, excludes?: ReadonlyArray<string>, includes?: ReadonlyArray<string>, depth?: number): string[] {
|
|
//throw new Error("readDirectory no supported!");
|
|
return tsi.matchFiles(path, extensions, excludes, includes, true, getCurrentDirectory(), depth, tsi.getAccessibleFileSystemEntries, tsi.realpath);
|
|
}
|
|
|
|
function exit(exitCode?: number): void {
|
|
throw new Error("exit with code:" + exitCode);
|
|
}
|
|
|
|
function getExecutingFilePath(): string {
|
|
return getCurrentDirectory() + "Content/JavaScript/PuertsEditor/node_modules/typescript/lib/tsc.js";
|
|
}
|
|
|
|
function getCurrentDirectory(): string {
|
|
return UE.FileSystemOperation.GetCurrentDirectory();
|
|
}
|
|
|
|
function getDirectories(path: string): string[] {
|
|
let result: string[] = [];
|
|
let dirs = UE.FileSystemOperation.GetDirectories(path);
|
|
for(var i=0; i < dirs.Num(); i++) {
|
|
result.push(dirs.Get(i));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//for debug only
|
|
/*return new Proxy({}, {
|
|
get: function(target, name) {
|
|
if (!(name in target)) {
|
|
if (typeof name === 'string') {
|
|
if (!(name in tgamejsSystem)) {
|
|
return undefined;
|
|
}
|
|
let maybeFunc = tgamejsSystem[name];
|
|
if (typeof maybeFunc === 'function') {
|
|
target[name] = function(...args: any[]) {
|
|
const res = maybeFunc(...args);
|
|
console.log("method:", name, ", args:", JSON.stringify(args), ",res:", res);
|
|
return res;
|
|
}
|
|
} else {
|
|
target[name] = tgamejsSystem[name];
|
|
}
|
|
}
|
|
}
|
|
|
|
return target[name]
|
|
}
|
|
}) as ts.System;*/
|
|
|
|
return customSystem;
|
|
}
|
|
|
|
let customSystem = getCustomSystem()
|
|
if (!ts.sys) {
|
|
let t : any = ts;
|
|
t.sys = customSystem;
|
|
}
|
|
|
|
function logErrors(allDiagnostics: readonly ts.Diagnostic[]) {
|
|
allDiagnostics.forEach(diagnostic => {
|
|
let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
if (diagnostic.file) {
|
|
let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
|
|
diagnostic.start!
|
|
);
|
|
console.error(` Error ${diagnostic.file.fileName} (${line + 1},${character +1}): ${message}`);
|
|
} else {
|
|
console.error(` Error: ${message}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
type PinCategory = "bool" | "class" | "int64" | "string" | "object" | "struct" | "float" | "enum" | "softobject" | "softclass";
|
|
|
|
const FunctionFlags = {
|
|
FUNC_None : 0x00000000,
|
|
|
|
FUNC_Final : 0x00000001, // Function is final (prebindable, non-overridable function).
|
|
FUNC_RequiredAPI : 0x00000002, // Indicates this function is DLL exported/imported.
|
|
FUNC_BlueprintAuthorityOnly: 0x00000004, // Function will only run if the object has network authority
|
|
FUNC_BlueprintCosmetic : 0x00000008, // Function is cosmetic in nature and should not be invoked on dedicated servers
|
|
// FUNC_ : 0x00000010, // unused.
|
|
// FUNC_ : 0x00000020, // unused.
|
|
FUNC_Net : 0x00000040, // Function is network-replicated.
|
|
FUNC_NetReliable : 0x00000080, // Function should be sent reliably on the network.
|
|
FUNC_NetRequest : 0x00000100, // Function is sent to a net service
|
|
FUNC_Exec : 0x00000200, // Executable from command line.
|
|
FUNC_Native : 0x00000400, // Native function.
|
|
FUNC_Event : 0x00000800, // Event function.
|
|
FUNC_NetResponse : 0x00001000, // Function response from a net service
|
|
FUNC_Static : 0x00002000, // Static function.
|
|
FUNC_NetMulticast : 0x00004000, // Function is networked multicast Server -> All Clients
|
|
FUNC_UbergraphFunction : 0x00008000, // Function is used as the merge 'ubergraph' for a blueprint, only assigned when using the persistent 'ubergraph' frame
|
|
FUNC_MulticastDelegate : 0x00010000, // Function is a multi-cast delegate signature (also requires FUNC_Delegate to be set!)
|
|
FUNC_Public : 0x00020000, // Function is accessible in all classes (if overridden, parameters must remain unchanged).
|
|
FUNC_Private : 0x00040000, // Function is accessible only in the class it is defined in (cannot be overridden, but function name may be reused in subclasses. IOW: if overridden, parameters don't need to match, and Super.Func() cannot be accessed since it's private.)
|
|
FUNC_Protected : 0x00080000, // Function is accessible only in the class it is defined in and subclasses (if overridden, parameters much remain unchanged).
|
|
FUNC_Delegate : 0x00100000, // Function is delegate signature (either single-cast or multi-cast, depending on whether FUNC_MulticastDelegate is set.)
|
|
FUNC_NetServer : 0x00200000, // Function is executed on servers (set by replication code if passes check)
|
|
FUNC_HasOutParms : 0x00400000, // function has out (pass by reference) parameters
|
|
FUNC_HasDefaults : 0x00800000, // function has structs that contain defaults
|
|
FUNC_NetClient : 0x01000000, // function is executed on clients
|
|
FUNC_DLLImport : 0x02000000, // function is imported from a DLL
|
|
FUNC_BlueprintCallable : 0x04000000, // function can be called from blueprint code
|
|
FUNC_BlueprintEvent : 0x08000000, // function can be overridden/implemented from a blueprint
|
|
FUNC_BlueprintPure : 0x10000000, // function can be called from blueprint code, and is also pure (produces no side effects). If you set this, you should set FUNC_BlueprintCallable as well.
|
|
FUNC_EditorOnly : 0x20000000, // function can only be called from an editor scrippt.
|
|
FUNC_Const : 0x40000000, // function can be called from blueprint code, and only reads state (never writes state)
|
|
FUNC_NetValidate : 0x80000000, // function must supply a _Validate implementation
|
|
|
|
FUNC_AllFlags : 0xFFFFFFFF,
|
|
};
|
|
|
|
const PropertyFlags = {
|
|
CPF_None : 0,
|
|
|
|
CPF_Edit : 0x0000000000000001, ///< Property is user-settable in the editor.
|
|
CPF_ConstParm : 0x0000000000000002, ///< This is a constant function parameter
|
|
CPF_BlueprintVisible : 0x0000000000000004, ///< This property can be read by blueprint code
|
|
CPF_ExportObject : 0x0000000000000008, ///< Object can be exported with actor.
|
|
CPF_BlueprintReadOnly : 0x0000000000000010, ///< This property cannot be modified by blueprint code
|
|
CPF_Net : 0x0000000000000020, ///< Property is relevant to network replication.
|
|
CPF_EditFixedSize : 0x0000000000000040, ///< Indicates that elements of an array can be modified, but its size cannot be changed.
|
|
CPF_Parm : 0x0000000000000080, ///< Function/When call parameter.
|
|
CPF_OutParm : 0x0000000000000100, ///< Value is copied out after function call.
|
|
CPF_ZeroConstructor : 0x0000000000000200, ///< memset is fine for construction
|
|
CPF_ReturnParm : 0x0000000000000400, ///< Return value.
|
|
CPF_DisableEditOnTemplate : 0x0000000000000800, ///< Disable editing of this property on an archetype/sub-blueprint
|
|
//CPF_ : 0x0000000000001000, ///<
|
|
CPF_Transient : 0x0000000000002000, ///< Property is transient: shouldn't be saved or loaded, except for Blueprint CDOs.
|
|
CPF_Config : 0x0000000000004000, ///< Property should be loaded/saved as permanent profile.
|
|
//CPF_ : 0x0000000000008000, ///<
|
|
CPF_DisableEditOnInstance : 0x0000000000010000, ///< Disable editing on an instance of this class
|
|
CPF_EditConst : 0x0000000000020000, ///< Property is uneditable in the editor.
|
|
CPF_GlobalConfig : 0x0000000000040000, ///< Load config from base class, not subclass.
|
|
CPF_InstancedReference : 0x0000000000080000, ///< Property is a component references.
|
|
//CPF_ : 0x0000000000100000, ///<
|
|
CPF_DuplicateTransient : 0x0000000000200000, ///< Property should always be reset to the default value during any type of duplication (copy/paste, binary duplication, etc.)
|
|
CPF_SubobjectReference : 0x0000000000400000, ///< Property contains subobject references (TSubobjectPtr)
|
|
//CPF_ : 0x0000000000800000, ///<
|
|
CPF_SaveGame : 0x0000000001000000, ///< Property should be serialized for save games, this is only checked for game-specific archives with ArIsSaveGame
|
|
CPF_NoClear : 0x0000000002000000, ///< Hide clear (and browse) button.
|
|
//CPF_ : 0x0000000004000000, ///<
|
|
CPF_ReferenceParm : 0x0000000008000000, ///< Value is passed by reference; CPF_OutParam and CPF_Param should also be set.
|
|
CPF_BlueprintAssignable : 0x0000000010000000, ///< MC Delegates only. Property should be exposed for assigning in blueprint code
|
|
CPF_Deprecated : 0x0000000020000000, ///< Property is deprecated. Read it from an archive, but don't save it.
|
|
CPF_IsPlainOldData : 0x0000000040000000, ///< If this is set, then the property can be memcopied instead of CopyCompleteValue / CopySingleValue
|
|
CPF_RepSkip : 0x0000000080000000, ///< Not replicated. For non replicated properties in replicated structs
|
|
CPF_RepNotify : 0x0000000100000000, ///< Notify actors when a property is replicated
|
|
CPF_Interp : 0x0000000200000000, ///< interpolatable property for use with matinee
|
|
CPF_NonTransactional : 0x0000000400000000, ///< Property isn't transacted
|
|
CPF_EditorOnly : 0x0000000800000000, ///< Property should only be loaded in the editor
|
|
CPF_NoDestructor : 0x0000001000000000, ///< No destructor
|
|
//CPF_ : 0x0000002000000000, ///<
|
|
CPF_AutoWeak : 0x0000004000000000, ///< Only used for weak pointers, means the export type is autoweak
|
|
CPF_ContainsInstancedReference : 0x0000008000000000, ///< Property contains component references.
|
|
CPF_AssetRegistrySearchable : 0x0000010000000000, ///< asset instances will add properties with this flag to the asset registry automatically
|
|
CPF_SimpleDisplay : 0x0000020000000000, ///< The property is visible by default in the editor details view
|
|
CPF_AdvancedDisplay : 0x0000040000000000, ///< The property is advanced and not visible by default in the editor details view
|
|
CPF_Protected : 0x0000080000000000, ///< property is protected from the perspective of script
|
|
CPF_BlueprintCallable : 0x0000100000000000, ///< MC Delegates only. Property should be exposed for calling in blueprint code
|
|
CPF_BlueprintAuthorityOnly : 0x0000200000000000, ///< MC Delegates only. This delegate accepts (only in blueprint) only events with BlueprintAuthorityOnly.
|
|
CPF_TextExportTransient : 0x0000400000000000, ///< Property shouldn't be exported to text format (e.g. copy/paste)
|
|
CPF_NonPIEDuplicateTransient : 0x0000800000000000, ///< Property should only be copied in PIE
|
|
CPF_ExposeOnSpawn : 0x0001000000000000, ///< Property is exposed on spawn
|
|
CPF_PersistentInstance : 0x0002000000000000, ///< A object referenced by the property is duplicated like a component. (Each actor should have an own instance.)
|
|
CPF_UObjectWrapper : 0x0004000000000000, ///< Property was parsed as a wrapper class like TSubclassOf<T>, FScriptInterface etc., rather than a USomething*
|
|
CPF_HasGetValueTypeHash : 0x0008000000000000, ///< This property can generate a meaningful hash value.
|
|
CPF_NativeAccessSpecifierPublic : 0x0010000000000000, ///< Public native access specifier
|
|
CPF_NativeAccessSpecifierProtected : 0x0020000000000000, ///< Protected native access specifier
|
|
CPF_NativeAccessSpecifierPrivate : 0x0040000000000000, ///< Private native access specifier
|
|
CPF_SkipSerialization : 0x0080000000000000, ///< Property shouldn't be serialized, can still be exported to text
|
|
};
|
|
|
|
const ELifetimeCondition = {
|
|
"COND_InitialOnly" : 1 , // This property will only attempt to send on the initial bunch
|
|
"COND_OwnerOnly" : 2 , // This property will only send to the actor's owner
|
|
"COND_SkipOwner" : 3 , // This property send to every connection EXCEPT the owner
|
|
"COND_SimulatedOnly" : 4 , // This property will only send to simulated actors
|
|
"COND_AutonomousOnly" : 5 , // This property will only send to autonomous actors
|
|
"COND_SimulatedOrPhysics" : 6 , // This property will send to simulated OR bRepPhysics actors
|
|
"COND_InitialOrOwner" : 7 , // This property will send on the initial packet, or to the actors owner
|
|
"COND_Custom" : 8 , // This property has no particular condition, but wants the ability to toggle on/off via SetCustomIsActiveOverride
|
|
"COND_ReplayOrOwner" : 9 , // This property will only send to the replay connection, or to the actors owner
|
|
"COND_ReplayOnly" : 10 , // This property will only send to the replay connection
|
|
"COND_SimulatedOnlyNoReplay" : 11 , // This property will send to actors only, but not to replay connections
|
|
"COND_SimulatedOrPhysicsNoReplay" : 12 , // This property will send to simulated Or bRepPhysics actors, but not to replay connections
|
|
"COND_SkipReplay" : 13 , // This property will not send to the replay connection
|
|
"COND_Never" : 15 , // This property will never be replicated
|
|
};
|
|
|
|
|
|
|
|
declare var global:any
|
|
|
|
function readAndParseConfigFile(configFilePath: string) : ts.ParsedCommandLine {
|
|
let readResult = ts.readConfigFile(configFilePath, customSystem.readFile);
|
|
|
|
return ts.parseJsonConfigFileContent(readResult.config, {
|
|
useCaseSensitiveFileNames: true,
|
|
readDirectory: customSystem.readDirectory,
|
|
fileExists: customSystem.fileExists,
|
|
readFile: customSystem.readFile,
|
|
trace: s => console.log(s)
|
|
}, customSystem.getCurrentDirectory());
|
|
}
|
|
|
|
function watch(configFilePath:string) {
|
|
let {fileNames, options} = readAndParseConfigFile(configFilePath);
|
|
|
|
console.log("start watch..", JSON.stringify({fileNames:fileNames, options: options}));
|
|
const versionsFilePath = tsi.getDirectoryPath(configFilePath) + "/ts_file_versions_info.json";
|
|
const fileVersions: ts.MapLike<{ version: string, processed: boolean, isBP?: boolean }> = {};
|
|
|
|
let beginTime = new Date().getTime();
|
|
fileNames.forEach(fileName => {
|
|
fileVersions[fileName] = { version: UE.FileSystemOperation.FileMD5Hash(fileName), processed: false, isBP: false};
|
|
});
|
|
console.log ("calc md5 using " + (new Date().getTime() - beginTime) + "ms");
|
|
|
|
function getDefaultLibLocation(): string {
|
|
return tsi.getDirectoryPath(tsi.normalizePath(customSystem.getExecutingFilePath()));
|
|
}
|
|
|
|
const scriptSnapshotsCache = new Map<string, {version: string, scriptSnapshot:ts.IScriptSnapshot}>();
|
|
|
|
// Create the language service host to allow the LS to communicate with the host
|
|
const servicesHost: ts.LanguageServiceHost = {
|
|
getScriptFileNames: () => fileNames,
|
|
getScriptVersion: fileName => {
|
|
if(fileName in fileVersions) {
|
|
return fileVersions[fileName] && fileVersions[fileName].version.toString();
|
|
} else {
|
|
let md5 = UE.FileSystemOperation.FileMD5Hash(fileName);
|
|
fileVersions[fileName] = { version: md5, processed: false};
|
|
return md5;
|
|
}
|
|
},
|
|
getScriptSnapshot: fileName => {
|
|
if (!customSystem.fileExists(fileName)) {
|
|
console.error("getScriptSnapshot: file not existed! path=" + fileName);
|
|
return undefined;
|
|
}
|
|
|
|
if (!(fileName in fileVersions)) {
|
|
fileVersions[fileName] = {version: UE.FileSystemOperation.FileMD5Hash(fileName), processed: false};
|
|
}
|
|
|
|
if (!scriptSnapshotsCache.has(fileName)) {
|
|
const sourceFile = customSystem.readFile(fileName);
|
|
if (!sourceFile) {
|
|
console.error("getScriptSnapshot: read file failed! path=" + fileName);
|
|
return undefined;
|
|
}
|
|
scriptSnapshotsCache.set(fileName, {
|
|
version:fileVersions[fileName].version,
|
|
scriptSnapshot: ts.ScriptSnapshot.fromString(sourceFile)
|
|
});
|
|
}
|
|
|
|
let scriptSnapshotsInfo = scriptSnapshotsCache.get(fileName);
|
|
|
|
if (scriptSnapshotsInfo.version != fileVersions[fileName].version) {
|
|
const sourceFile = customSystem.readFile(fileName);
|
|
if (!sourceFile) {
|
|
console.error("getScriptSnapshot: read file failed! path=" + fileName);
|
|
return undefined;
|
|
}
|
|
scriptSnapshotsInfo.version = fileVersions[fileName].version;
|
|
scriptSnapshotsInfo.scriptSnapshot = ts.ScriptSnapshot.fromString(sourceFile);
|
|
}
|
|
//console.log("getScriptSnapshot:"+ fileName + ",in:" + new Error().stack)
|
|
|
|
return scriptSnapshotsInfo.scriptSnapshot;
|
|
},
|
|
getCurrentDirectory: customSystem.getCurrentDirectory,
|
|
getCompilationSettings: () => options,
|
|
getDefaultLibFileName: options => tsi.combinePaths(getDefaultLibLocation(), ts.getDefaultLibFileName(options)),
|
|
fileExists: ts.sys.fileExists,
|
|
readFile: ts.sys.readFile,
|
|
readDirectory: ts.sys.readDirectory,
|
|
directoryExists: ts.sys.directoryExists,
|
|
getDirectories: ts.sys.getDirectories,
|
|
};
|
|
|
|
let service = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
|
|
|
|
function getProgramFromService() {
|
|
while(true) {
|
|
try {
|
|
return service.getProgram();
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
//异常了从新创建Language Service,有可能不断失败,UE的文件读取偶尔会失败,失败后ts增量编译会不断的在tryReuseStructureFromOldProgram那断言失败
|
|
service = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
|
|
}
|
|
}
|
|
|
|
let pendingBlueprintRefleshJobs: Array<{type:ts.Type, op:() =>void}> = [];
|
|
function isChildOf(a: ts.Type, b: ts.Type): boolean {
|
|
let baseTypes = a.getBaseTypes();
|
|
if (baseTypes.indexOf(b as ts.BaseType) >= 0) {
|
|
return true;
|
|
}
|
|
for(let i = 0; i < baseTypes.length; ++i) {
|
|
if (isChildOf(baseTypes[i], b)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function topologicalSort(classes:Array<{type:ts.Type, op:() =>void}>) {
|
|
const visited = new Set();
|
|
const result: Array<{type:ts.Type, op:() =>void}> = [];
|
|
|
|
function visit(node:{type:ts.Type, op:() =>void}) {
|
|
if (!visited.has(node)) {
|
|
visited.add(node);
|
|
for (const other of classes) {
|
|
if (isChildOf(node.type, other.type)) {
|
|
visit(other);
|
|
}
|
|
}
|
|
result.push(node);
|
|
}
|
|
}
|
|
|
|
for (const cls of classes) {
|
|
visit(cls);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
function refreshBlueprints() {
|
|
if (pendingBlueprintRefleshJobs.length > 0) {
|
|
pendingBlueprintRefleshJobs = topologicalSort(pendingBlueprintRefleshJobs);
|
|
pendingBlueprintRefleshJobs.forEach(job => {
|
|
job.op();
|
|
});
|
|
pendingBlueprintRefleshJobs = [];
|
|
}
|
|
}
|
|
|
|
beginTime = new Date().getTime();
|
|
let program = getProgramFromService();
|
|
console.log ("full compile using " + (new Date().getTime() - beginTime) + "ms");
|
|
let diagnostics = ts.getPreEmitDiagnostics(program);
|
|
let restoredFileVersions: ts.MapLike<{ version: string, processed: boolean, isBP: boolean }> = {};
|
|
var changed = false;
|
|
const versionsFileExisted = customSystem.fileExists(versionsFilePath);
|
|
if (versionsFileExisted) {
|
|
try {
|
|
restoredFileVersions = JSON.parse(customSystem.readFile(versionsFilePath));
|
|
console.log("restore versions from ", versionsFilePath);
|
|
} catch {}
|
|
}
|
|
if (diagnostics.length > 0) {
|
|
fileNames.forEach(fileName => {
|
|
fileVersions[fileName] = restoredFileVersions[fileName] || fileVersions[fileName];
|
|
});
|
|
logErrors(diagnostics);
|
|
} else {
|
|
function getClassPathInfo(sourceFilePath: string): {moduleFileName:string, modulePath:string} {
|
|
let modulePath:string = undefined;
|
|
let moduleFileName:string = undefined;
|
|
if(sourceFilePath.endsWith(".ts")) {
|
|
if (options.baseUrl && sourceFilePath.startsWith(options.baseUrl)) {
|
|
moduleFileName = sourceFilePath.substr(options.baseUrl.length + 1);
|
|
modulePath = tsi.getDirectoryPath(moduleFileName);
|
|
moduleFileName = tsi.removeExtension(moduleFileName, ".ts");
|
|
}
|
|
}
|
|
return {moduleFileName, modulePath};
|
|
}
|
|
fileNames.forEach(fileName => {
|
|
if (!(fileName in restoredFileVersions) || restoredFileVersions[fileName].version != fileVersions[fileName].version || !restoredFileVersions[fileName].processed) {
|
|
onSourceFileAddOrChange(fileName, false, program, true, false);
|
|
changed = true;
|
|
} else {
|
|
fileVersions[fileName].processed = true;
|
|
}
|
|
if (fileName in restoredFileVersions) {
|
|
fileVersions[fileName].isBP = restoredFileVersions[fileName].isBP;
|
|
}
|
|
});
|
|
fileNames.forEach(fileName => {
|
|
if (versionsFileExisted && (!(fileName in restoredFileVersions))) return;
|
|
if (versionsFileExisted && (!restoredFileVersions[fileName].isBP)) return;
|
|
const {moduleFileName, modulePath} = getClassPathInfo(fileName);
|
|
let BPExisted = false;
|
|
if (moduleFileName) {
|
|
BPExisted = UE.PEBlueprintAsset.Existed(moduleFileName, modulePath);
|
|
}
|
|
if (!versionsFileExisted || restoredFileVersions[fileName].version != fileVersions[fileName].version || !restoredFileVersions[fileName].processed || !BPExisted) {
|
|
onSourceFileAddOrChange(fileName, false, program, false);
|
|
changed = true;
|
|
} else {
|
|
fileVersions[fileName].processed = true;
|
|
}
|
|
});
|
|
refreshBlueprints();
|
|
if (changed) {
|
|
UE.FileSystemOperation.WriteFile(versionsFilePath, JSON.stringify(fileVersions, null, 4));
|
|
}
|
|
}
|
|
|
|
var dirWatcher = new UE.PEDirectoryWatcher();
|
|
global.__dirWatcher = dirWatcher; //防止被释放?
|
|
|
|
dirWatcher.OnChanged.Add((added, modified, removed) => {
|
|
setTimeout(() =>{
|
|
var changed = false;
|
|
let modifiedFiles: Array<string> = [];
|
|
for(var i = 0; i < modified.Num(); i++) {
|
|
modifiedFiles.push(modified.Get(i));
|
|
}
|
|
let removedSet = new Set<string>();
|
|
for(var i = 0; i < removed.Num(); i++) {
|
|
removedSet.add(removed.Get(i));
|
|
}
|
|
let addFiles: Array<string> = [];
|
|
for(var i = 0; i < added.Num(); i++) {
|
|
let fileName = added.Get(i);
|
|
//remove and add is the same as modified
|
|
if (removedSet.has(fileName)) {
|
|
modifiedFiles.push(fileName);
|
|
} else {
|
|
addFiles.push(fileName);
|
|
}
|
|
}
|
|
if (addFiles.length > 0) {
|
|
onFileAdded();
|
|
changed = true;
|
|
}
|
|
modifiedFiles.forEach(fileName => {
|
|
if (fileName in fileVersions) {
|
|
let md5 = UE.FileSystemOperation.FileMD5Hash(fileName);
|
|
if (md5 === fileVersions[fileName].version) {
|
|
console.log(fileName + " md5 not changed, so skiped!");
|
|
} else {
|
|
console.log(`${fileName} md5 from ${fileVersions[fileName].version} to ${md5}`);
|
|
fileVersions[fileName].version = md5;
|
|
onSourceFileAddOrChange(fileName, true);
|
|
changed = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
refreshBlueprints();
|
|
if (changed) {
|
|
console.log("versions saved to " + versionsFilePath);
|
|
UE.FileSystemOperation.WriteFile(versionsFilePath, JSON.stringify(fileVersions, null, 4));
|
|
}
|
|
}, 100);//延时100毫秒,防止因为读冲突而文件读取失败
|
|
});
|
|
|
|
dirWatcher.Watch(customSystem.getCurrentDirectory());
|
|
|
|
function onFileAdded(): void {
|
|
let cmdLine = readAndParseConfigFile(configFilePath);
|
|
let newFiles: Array<string> = [];
|
|
cmdLine.fileNames.forEach(fileName => {
|
|
if (!(fileName in fileVersions)) {
|
|
console.log(`new file: ${fileName} ...`)
|
|
newFiles.push(fileName);
|
|
fileVersions[fileName] = { version: UE.FileSystemOperation.FileMD5Hash(fileName), processed: false };
|
|
}
|
|
});
|
|
|
|
if (newFiles.length > 0) {
|
|
fileNames = cmdLine.fileNames;
|
|
options = cmdLine.options;
|
|
program = getProgramFromService();
|
|
|
|
newFiles.forEach(fileName => onSourceFileAddOrChange(fileName, true, program));
|
|
}
|
|
}
|
|
|
|
function onSourceFileAddOrChange(sourceFilePath: string, reload: boolean, program?: ts.Program, doEmitJs: boolean = true, doEmitBP:boolean = true) {
|
|
if (!program) {
|
|
let beginTime = new Date().getTime();
|
|
program = getProgramFromService();
|
|
console.log("incremental compile " + sourceFilePath + " using " + (new Date().getTime() - beginTime) + "ms");
|
|
}
|
|
|
|
let sourceFile = program.getSourceFile(sourceFilePath);
|
|
|
|
if (sourceFile) {
|
|
const diagnostics = [
|
|
...program.getSyntacticDiagnostics(sourceFile),
|
|
...program.getSemanticDiagnostics(sourceFile)
|
|
];
|
|
|
|
let checker = program.getTypeChecker();
|
|
|
|
if (diagnostics.length > 0) {
|
|
logErrors(diagnostics);
|
|
} else {
|
|
if (doEmitBP) {
|
|
fileVersions[sourceFilePath].isBP = false;
|
|
}
|
|
fileVersions[sourceFilePath].processed = true;
|
|
if (!sourceFile.isDeclarationFile) {
|
|
let emitOutput = service.getEmitOutput(sourceFilePath);
|
|
|
|
if (!emitOutput.emitSkipped) {
|
|
let modulePath:string = undefined;
|
|
let moduleFileName:string = undefined;
|
|
let jsSource:string = undefined;
|
|
emitOutput.outputFiles.forEach(output => {
|
|
if (doEmitJs) {
|
|
console.log(`write ${output.name} ...` )
|
|
UE.FileSystemOperation.WriteFile(output.name, output.text);
|
|
}
|
|
|
|
if (output.name.endsWith(".js") || output.name.endsWith(".mjs")) {
|
|
jsSource = output.text;
|
|
if (options.outDir && output.name.startsWith(options.outDir)) {
|
|
moduleFileName = output.name.substr(options.outDir.length + 1);
|
|
modulePath = tsi.getDirectoryPath(moduleFileName);
|
|
moduleFileName = tsi.removeExtension(moduleFileName, output.name.endsWith(".js") ? ".js" : ".mjs");
|
|
}
|
|
}
|
|
});
|
|
if (moduleFileName && reload) {
|
|
UE.FileSystemOperation.PuertsNotifyChange(moduleFileName, jsSource);
|
|
}
|
|
|
|
if (!doEmitBP) return;
|
|
|
|
let foundType: ts.Type = undefined;
|
|
let foundBaseTypeUClass: UE.Class = undefined;
|
|
ts.forEachChild(sourceFile, (node) => {
|
|
if (ts.isExportAssignment(node) && ts.isIdentifier(node.expression)) {
|
|
const type = checker.getTypeAtLocation(node.expression);
|
|
if (!type || !type.getSymbol()) return;
|
|
if (type.getSymbol().getName() != tsi.getBaseFileName(moduleFileName)) {
|
|
//console.error("type name must the same as file name!");
|
|
return;
|
|
}
|
|
|
|
let baseTypes = type.getBaseTypes();
|
|
if (!baseTypes || baseTypes.length != 1) return;
|
|
let structOfType = getUClassOfType(baseTypes[0]);
|
|
let baseTypeUClass:UE.Class = undefined;
|
|
|
|
if(!structOfType){
|
|
return
|
|
}
|
|
|
|
if (structOfType.GetClass().IsChildOf(UE.Class.StaticClass())) {
|
|
baseTypeUClass = structOfType as UE.Class;
|
|
}
|
|
else {
|
|
console.warn("do not support UStruct:" + checker.typeToString(type));
|
|
return;
|
|
}
|
|
|
|
if (baseTypeUClass) {
|
|
if (isSubclassOf(type, "Subsystem")) {
|
|
console.error("do not support Subsystem " + checker.typeToString(type));
|
|
return;
|
|
}
|
|
if (!baseTypeUClass.IsNative()) {
|
|
let moduleNames = getModuleNames(baseTypes[0]);
|
|
if (moduleNames.length > 1 && moduleNames[0] == 'ue') {
|
|
console.error(`${checker.typeToString(type)} extends a blueprint`);
|
|
return;
|
|
}
|
|
}
|
|
|
|
foundType = type;
|
|
foundBaseTypeUClass = baseTypeUClass;
|
|
} else {
|
|
console.warn("can not find base for " + checker.typeToString(type));
|
|
}
|
|
}
|
|
});
|
|
|
|
if (foundType && foundBaseTypeUClass) {
|
|
fileVersions[sourceFilePath].isBP = true;
|
|
//onBlueprintTypeAddOrChange(foundBaseTypeUClass, foundType, modulePath);
|
|
pendingBlueprintRefleshJobs.push({ type: foundType, op: () => onBlueprintTypeAddOrChange(foundBaseTypeUClass, foundType, modulePath) });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function typeNameToString(node: ts.EntityName): string {
|
|
if (ts.isIdentifier(node)) {
|
|
return node.text;
|
|
}
|
|
else {
|
|
return node.right.text;
|
|
}
|
|
}
|
|
|
|
function isSubclassOf(type:ts.Type, baseTypeName:string) : boolean {
|
|
let baseTypes = type.getBaseTypes();
|
|
if (baseTypes.length != 1) return false;
|
|
if (baseTypes[0].getSymbol().getName() == baseTypeName) {
|
|
return true;
|
|
}
|
|
return isSubclassOf(baseTypes[0], baseTypeName);
|
|
}
|
|
|
|
function getUClassOfType(type: ts.Type) : UE.Object {
|
|
if (!type) return undefined;
|
|
let moduleNames = getModuleNames(type);
|
|
if (moduleNames.length > 0 && moduleNames[0] == 'ue') {
|
|
if (moduleNames.length == 1) {
|
|
try {
|
|
let jsCls = (UE as any)[type.symbol.getName()];
|
|
if (typeof jsCls.StaticClass == 'function') {
|
|
return jsCls.StaticClass();
|
|
}
|
|
} catch (e) {
|
|
console.error(`load ue type [${type.symbol.getName()}], throw: ${e}`);
|
|
}
|
|
} else if (moduleNames.length == 2) {
|
|
let classPath = '/' + moduleNames[1] + '.' + type.symbol.getName();
|
|
return (UE.Field.Load as any)(classPath, true);
|
|
}
|
|
} else if ( type.symbol && type.symbol.valueDeclaration) {
|
|
//eturn undefined;
|
|
let baseTypes = type.getBaseTypes();
|
|
if (!baseTypes || baseTypes.length != 1) return undefined;
|
|
let baseTypeUClass = getUClassOfType(baseTypes[0]);
|
|
if (!baseTypeUClass) return undefined;
|
|
|
|
//console.error("modulePath:", getModulePath(type.symbol.valueDeclaration.getSourceFile().fileName));
|
|
let sourceFile = type.symbol.valueDeclaration.getSourceFile();
|
|
let sourceFileName: string
|
|
program.emit(sourceFile, writeFile, undefined, false, undefined);
|
|
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) {
|
|
if (fileName.endsWith('.js')) {
|
|
sourceFileName = tsi.removeExtension(fileName, '.js');
|
|
}
|
|
}
|
|
if ( tsi.getBaseFileName(sourceFileName) != type.symbol.getName()) {
|
|
console.error("type name must the same as file name!");
|
|
return undefined;
|
|
}
|
|
|
|
if (options.outDir && sourceFileName.startsWith(options.outDir)) {
|
|
let moduleFileName = sourceFileName.substr(options.outDir.length + 1);
|
|
let modulePath = tsi.getDirectoryPath(moduleFileName);
|
|
let bp = new UE.PEBlueprintAsset();
|
|
bp.LoadOrCreate(type.getSymbol().getName(), modulePath, baseTypeUClass as UE.Class, 0, 0);
|
|
bp.Save();
|
|
return bp.GeneratedClass;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getSymbolTypeNode(symbol: ts.Symbol) : ts.Node {
|
|
if (symbol.valueDeclaration) {
|
|
for(var i = symbol.valueDeclaration.getChildCount() - 1 ; i >= 0; i--) {
|
|
var child = symbol.valueDeclaration.getChildAt(i);
|
|
if (child.kind == ts.SyntaxKind.TypeReference) {
|
|
return child;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function tsTypeToPinType(type: ts.Type, node: ts.Node) : { pinType: UE.PEGraphPinType, pinValueType?: UE.PEGraphTerminalType} | undefined {
|
|
if (!type) return undefined;
|
|
try {
|
|
let typeNode = checker.typeToTypeNode(type, undefined, undefined);
|
|
//console.log(checker.typeToString(type), tds)
|
|
if (ts.isTypeReferenceNode(typeNode) && type.symbol) {
|
|
let typeName = type.symbol.getName();
|
|
if (typeName == 'BigInt') {
|
|
let category:PinCategory = "int64";
|
|
let pinType = new UE.PEGraphPinType(category, undefined, UE.EPinContainerType.None, false, false);
|
|
return {pinType: pinType};
|
|
}
|
|
if (!typeNode.typeArguments || typeNode.typeArguments.length == 0) {
|
|
let category:PinCategory = "object";
|
|
let uclass = getUClassOfType(type);
|
|
if (!uclass) {
|
|
let uenum = UE.Enum.Find(type.symbol.getName());
|
|
if (uenum) {
|
|
return {pinType: new UE.PEGraphPinType("byte", uenum, UE.EPinContainerType.None, false, false)};
|
|
}
|
|
console.warn("can not find type of " + typeName);
|
|
return undefined;
|
|
}
|
|
|
|
let pinType = new UE.PEGraphPinType(category, uclass, UE.EPinContainerType.None, false, false);
|
|
return {pinType: pinType};
|
|
} else { //TArray, TSet, TMap
|
|
let typeRef = type as ts.TypeReference;
|
|
var children: ts.Node[] = [];
|
|
|
|
let typeArguments = typeRef.typeArguments || typeRef.aliasTypeArguments;
|
|
|
|
if (typeRef.aliasTypeArguments && typeRef.aliasSymbol) {
|
|
typeName = typeRef.aliasSymbol.getName();
|
|
}
|
|
|
|
if (!typeArguments) {
|
|
console.warn("can not find type arguments of " + node.getFullText());
|
|
return undefined;
|
|
}
|
|
|
|
if (node) {
|
|
node.forEachChild(child => {
|
|
children.push(child);
|
|
})
|
|
}
|
|
|
|
let result = tsTypeToPinType(typeArguments[0], children[1]);
|
|
|
|
if (!result || result.pinType.PinContainerType != UE.EPinContainerType.None && typeName != '$Ref' && typeName != '$InRef') {
|
|
console.warn("can not find pin type of typeArguments[0] " + typeName);
|
|
return undefined;
|
|
}
|
|
|
|
if (children[1]) {
|
|
postProcessPinType(children[1], result.pinType, false);
|
|
}
|
|
|
|
if (typeName == 'TArray' || typeName == 'TSet') {
|
|
result.pinType.PinContainerType = typeName == 'TArray' ? UE.EPinContainerType.Array : UE.EPinContainerType.Set;
|
|
return result;
|
|
} else if (typeName == 'TSubclassOf') {
|
|
let category:PinCategory = "class";
|
|
result.pinType.PinCategory = category;
|
|
return result;
|
|
} else if (typeName == 'TSoftObjectPtr') {
|
|
let category:PinCategory = "softobject";
|
|
result.pinType.PinCategory = category;
|
|
return result;
|
|
} else if (typeName == 'TSoftClassPtr') {
|
|
let category:PinCategory = "softclass";
|
|
result.pinType.PinCategory = category;
|
|
return result;
|
|
} else if (typeName == '$Ref') {
|
|
result.pinType.bIsReference = true;
|
|
return result;
|
|
} else if (typeName == '$InRef') {
|
|
result.pinType.bIsReference = true;
|
|
result.pinType.bIn = true;
|
|
return result;
|
|
} else if (typeName == 'TMap') {
|
|
let valuePinType = tsTypeToPinType(typeArguments[1], undefined);
|
|
if (!valuePinType || valuePinType.pinType.PinContainerType != UE.EPinContainerType.None) {
|
|
console.warn("can not find pin type of typeArguments[1] " + typeName);
|
|
return undefined;
|
|
}
|
|
if (children[2]) {
|
|
postProcessPinType(children[2], valuePinType.pinType, false);
|
|
}
|
|
result.pinType.PinContainerType = UE.EPinContainerType.Map;
|
|
result.pinValueType = new UE.PEGraphTerminalType(valuePinType.pinType.PinCategory, valuePinType.pinType.PinSubCategoryObject);
|
|
return result;
|
|
} else {
|
|
console.warn("not support generic type: " + typeName);
|
|
return undefined;
|
|
}
|
|
}
|
|
} else {
|
|
//"bool" | "class" | "int64" | "string" | "object" | "struct" | "float";
|
|
let category:PinCategory;
|
|
|
|
switch (typeNode.kind) {
|
|
case ts.SyntaxKind.NumberKeyword:
|
|
category = "float";
|
|
break;
|
|
case ts.SyntaxKind.StringKeyword:
|
|
category = 'string';
|
|
break;
|
|
case ts.SyntaxKind.BigIntKeyword:
|
|
category = 'int64';
|
|
break;
|
|
case ts.SyntaxKind.BooleanKeyword:
|
|
category = 'bool';
|
|
break;
|
|
default:
|
|
console.warn("not support kind: " + typeNode.kind);
|
|
return undefined;
|
|
}
|
|
let pinType = new UE.PEGraphPinType(category, undefined, UE.EPinContainerType.None, false, false);
|
|
return {pinType: pinType};
|
|
}
|
|
} catch (e) {
|
|
console.error(e.stack || e);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function manualSkip(valueDeclaration: ts.Declaration): boolean {
|
|
const commentRanges = ts.getLeadingCommentRanges(
|
|
sourceFile.getFullText(),
|
|
valueDeclaration.getFullStart());
|
|
return !!(commentRanges && commentRanges.find(r => sourceFile.getFullText().slice(r.pos,r.end).indexOf("@no-blueprint" ) > 0)) || hasDecorator(valueDeclaration, "no_blueprint");
|
|
}
|
|
|
|
function tryGetAnnotation(valueDeclaration: ts.Node, key:string, leading: boolean): string {
|
|
const commentRanges = (leading ? ts.getLeadingCommentRanges : ts.getTrailingCommentRanges)(
|
|
sourceFile.getFullText(),
|
|
valueDeclaration.getFullStart() + (leading ? 0 : valueDeclaration.getFullWidth()));
|
|
if (commentRanges) {
|
|
let ret: string
|
|
commentRanges.forEach(r => {
|
|
let m = sourceFile.getFullText().slice(r.pos, r.end).match(new RegExp(`@${key}:([^*]*)`));
|
|
if (m) {
|
|
ret = m[1].trim();
|
|
}
|
|
});
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
function postProcessPinType(valueDeclaration: ts.Node, pinType: UE.PEGraphPinType, leading: boolean):void {
|
|
if (pinType.PinContainerType == UE.EPinContainerType.None) {
|
|
let pc = pinType.PinCategory;
|
|
if (pc === "float") {
|
|
let cppType = tryGetAnnotation(valueDeclaration, "cpp", leading);
|
|
if (cppType === "int" || cppType === "byte") {
|
|
pinType.PinCategory = cppType;
|
|
}
|
|
} else if (pc === "string") {
|
|
let cppType = tryGetAnnotation(valueDeclaration, "cpp", leading);
|
|
if (cppType === "name" || cppType === "text") {
|
|
pinType.PinCategory = cppType;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getFlagsValue(str: string, flagsDef:object):number {
|
|
if (!str) return 0;
|
|
return str.split("|").map(x => x.trim()).map(x => x in flagsDef ? flagsDef[x] as number : 0).reduce((x, y) => x | y);
|
|
}
|
|
|
|
function getDecoratorFlagsValue(valueDeclaration:ts.Node, posfix: string, flagsDef:object): bigint {
|
|
if (valueDeclaration && valueDeclaration.decorators) {
|
|
let decorators = valueDeclaration.decorators;
|
|
let ret:bigint = 0n;
|
|
decorators.forEach((decorator, index) => {
|
|
let expression = decorator.expression;
|
|
if (ts.isCallExpression(expression)) {
|
|
if (expression.expression.getFullText() == posfix|| expression.expression.getFullText().endsWith('.' + posfix)) {
|
|
expression.arguments.forEach((value, index) => {
|
|
let e = value.getFullText().split("|").map(x => x.trim().replace(/^.*[\.]/, ''))
|
|
.map(x => x in flagsDef ? BigInt(flagsDef[x]) : 0n)
|
|
.reduce((x, y) => BigInt(x) | BigInt(y));
|
|
ret = ret | e;
|
|
})
|
|
}
|
|
}
|
|
});
|
|
return ret;
|
|
} else {
|
|
return 0n;
|
|
}
|
|
}
|
|
|
|
function hasDecorator(valueDeclaration:ts.Node, posfix: string): boolean {
|
|
let ret = false;
|
|
if (valueDeclaration && valueDeclaration.decorators) {
|
|
let decorators = valueDeclaration.decorators;
|
|
decorators.forEach((decorator, index) => {
|
|
let expression = decorator.expression;
|
|
if (ts.isCallExpression(expression)) {
|
|
if (expression.expression.getFullText() == posfix|| expression.expression.getFullText().endsWith('.' + posfix)) {
|
|
ret = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function onBlueprintTypeAddOrChange(baseTypeUClass: UE.Class, type: ts.Type, modulePath:string) {
|
|
console.log(`gen blueprint for ${type.getSymbol().getName()}, path: ${modulePath}`);
|
|
let lsFunctionLibrary:boolean = baseTypeUClass && baseTypeUClass.GetName() === "BlueprintFunctionLibrary";
|
|
let bp = new UE.PEBlueprintAsset();
|
|
bp.LoadOrCreateWithMetaData(type.getSymbol().getName(), modulePath, baseTypeUClass, 0, 0, uemeta.compileClassMetaData(type));
|
|
let hasConstructor = false;
|
|
let properties: ts.Symbol[] = [];
|
|
type.symbol.valueDeclaration.forEachChild(x => {
|
|
if (ts.isMethodDeclaration(x) && !manualSkip(x)) {
|
|
let isStatic = !!(ts.getCombinedModifierFlags(x) & ts.ModifierFlags.Static);
|
|
if (isStatic && !lsFunctionLibrary) {
|
|
console.warn(`do not support static function [${x.name.getText()}]`);
|
|
return;
|
|
}
|
|
if (!isStatic && lsFunctionLibrary) {
|
|
console.warn(`do not support non-static function [${x.name.getText()}] in BlueprintFunctionLibrary`);
|
|
return;
|
|
}
|
|
if (x.name.getText() === 'ReceiveInit') {
|
|
if (baseTypeUClass == UE.GameInstance.StaticClass() || baseTypeUClass.IsChildOf(UE.GameInstance.StaticClass())) {
|
|
console.warn(`do not support override GameInstance.ReceiveInit in ${type.getSymbol().getName()}`);
|
|
return;
|
|
}
|
|
}
|
|
properties.push(checker.getSymbolAtLocation(x.name));
|
|
} else if (ts.isPropertyDeclaration(x) && !manualSkip(x)) {
|
|
let isStatic = !!(ts.getCombinedModifierFlags(x) & ts.ModifierFlags.Static);
|
|
if (isStatic) {
|
|
console.warn("static property:" + x.name.getText() + ' not support');
|
|
return;
|
|
}
|
|
properties.push(checker.getSymbolAtLocation(x.name));
|
|
}
|
|
});
|
|
let attachments = UE.NewMap(UE.BuiltinName, UE.BuiltinName);
|
|
properties
|
|
.filter(x => ts.isClassDeclaration(x.valueDeclaration.parent) && checker.getSymbolAtLocation(x.valueDeclaration.parent.name) == type.symbol)
|
|
.forEach((symbol) => {
|
|
if (ts.isMethodDeclaration(symbol.valueDeclaration!)) {
|
|
if (symbol.getName() === 'Constructor') {
|
|
hasConstructor = true;
|
|
return;
|
|
}
|
|
|
|
let methodType = checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration!);
|
|
let signatures = checker.getSignaturesOfType(methodType, ts.SignatureKind.Call);
|
|
if (!signatures) {
|
|
console.warn(`can not find signature for ${symbol.getName()} `);
|
|
return;
|
|
}
|
|
if (signatures.length != 1) {
|
|
console.warn(`find more than one signature for ${symbol.getName()} `);
|
|
return;
|
|
}
|
|
let signature = signatures[0];
|
|
|
|
for (var i = 0; i < signature.parameters.length; i++) {
|
|
let paramType:ts.Type = checker.getTypeOfSymbolAtLocation(signature.parameters[i], signature.parameters[i].valueDeclaration!);
|
|
let paramPinType = tsTypeToPinType(paramType, getSymbolTypeNode(signature.parameters[i]));
|
|
if (!paramPinType) {
|
|
console.warn(symbol.getName() + " of " + checker.typeToString(type) + " has not supported parameter!");
|
|
bp.ClearParameter();
|
|
return;
|
|
}
|
|
postProcessPinType(signature.parameters[i].valueDeclaration, paramPinType.pinType, false);
|
|
// bp.AddParameter(signature.parameters[i].getName(), paramPinType.pinType, paramPinType.pinValueType);
|
|
bp.AddParameterWithMetaData(signature.parameters[i].getName(), paramPinType.pinType, paramPinType.pinValueType, uemeta.compileParamMetaData(signature.parameters[i]));
|
|
}
|
|
|
|
//console.log("add function", symbol.getName());
|
|
let sflags = tryGetAnnotation(symbol.valueDeclaration, "flags", true);
|
|
let flags = getFlagsValue(sflags, FunctionFlags);
|
|
let clearFlags = 0;
|
|
|
|
if (symbol.valueDeclaration && symbol.valueDeclaration.decorators) {
|
|
flags |= Number(getDecoratorFlagsValue(symbol.valueDeclaration, "flags", FunctionFlags));
|
|
flags |= Number(getDecoratorFlagsValue(symbol.valueDeclaration, "set_flags", FunctionFlags));
|
|
clearFlags = Number(getDecoratorFlagsValue(symbol.valueDeclaration, "clear_flags", FunctionFlags));
|
|
}
|
|
|
|
if (symbol.valueDeclaration.type && (ts.SyntaxKind.VoidKeyword === symbol.valueDeclaration.type.kind)) {
|
|
// bp.AddFunction(symbol.getName(), true, undefined, undefined, flags, clearFlags);
|
|
bp.AddFunctionWithMetaData(symbol.getName(), true, undefined, undefined, flags, clearFlags, uemeta.compileFunctionMetaData(symbol));
|
|
} else {
|
|
let returnType = signature.getReturnType();
|
|
let resultPinType = tsTypeToPinType(returnType, getSymbolTypeNode(symbol));
|
|
if (!resultPinType) {
|
|
console.warn(symbol.getName() + " of " + checker.typeToString(type) + " has not supported return type!");
|
|
bp.ClearParameter();
|
|
return;
|
|
}
|
|
postProcessPinType(symbol.valueDeclaration, resultPinType.pinType, true);
|
|
|
|
// bp.AddFunction(symbol.getName(), false, resultPinType.pinType, resultPinType.pinValueType, flags, clearFlags);
|
|
bp.AddFunctionWithMetaData(symbol.getName(), false, resultPinType.pinType, resultPinType.pinValueType, flags, clearFlags, uemeta.compileFunctionMetaData(symbol));
|
|
}
|
|
bp.ClearParameter();
|
|
} else {
|
|
let propType = checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration!);
|
|
let propPinType = tsTypeToPinType(propType, getSymbolTypeNode(symbol));
|
|
if (!propPinType) {
|
|
console.warn(symbol.getName() + " of " + checker.typeToString(type) + " not support!");
|
|
} else {
|
|
postProcessPinType(symbol.valueDeclaration, propPinType.pinType, true);
|
|
//console.log("add member variable", symbol.getName());
|
|
let sflags = tryGetAnnotation(symbol.valueDeclaration, "flags", true);
|
|
let flags:bigint = BigInt(getFlagsValue(sflags, PropertyFlags));
|
|
let cond = 0;
|
|
if (symbol.valueDeclaration && symbol.valueDeclaration.decorators) {
|
|
cond = Number(getDecoratorFlagsValue(symbol.valueDeclaration, "condition", ELifetimeCondition));
|
|
if (cond != 0) {
|
|
flags = flags | BigInt(PropertyFlags.CPF_Net);
|
|
}
|
|
flags = flags | getDecoratorFlagsValue(symbol.valueDeclaration, "flags", PropertyFlags);
|
|
symbol.valueDeclaration.decorators.forEach((decorator) => {
|
|
let expression = decorator.expression;
|
|
if (ts.isCallExpression(expression)) {
|
|
if(expression.expression.getFullText().endsWith("uproperty.attach")) {
|
|
expression.arguments.forEach((value) => {
|
|
attachments.Add(symbol.getName(), value.getFullText().slice(1, -1));
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (!hasDecorator(symbol.valueDeclaration, "edit_on_instance")) {
|
|
flags = flags | BigInt(PropertyFlags.CPF_DisableEditOnInstance);
|
|
}
|
|
|
|
// bp.AddMemberVariable(symbol.getName(), propPinType.pinType, propPinType.pinValueType, Number(flags & 0xffffffffn), Number(flags >> 32n), cond);
|
|
bp.AddMemberVariableWithMetaData(symbol.getName(), propPinType.pinType, propPinType.pinValueType, Number(flags & 0xffffffffn), Number(flags >> 32n), cond, uemeta.compilePropertyMetaData(symbol));
|
|
}
|
|
}
|
|
});
|
|
bp.RemoveNotExistedComponent();
|
|
bp.RemoveNotExistedMemberVariable();
|
|
bp.RemoveNotExistedFunction();
|
|
bp.SetupAttachments(attachments);
|
|
bp.HasConstructor = hasConstructor;
|
|
bp.Save();
|
|
}
|
|
|
|
function getModuleNames(type: ts.Type) : string[] {
|
|
let ret:string[] = []
|
|
if(type.symbol && type.symbol.valueDeclaration && type.symbol.valueDeclaration.parent && ts.isModuleBlock(type.symbol.valueDeclaration.parent)) {
|
|
let moduleBody: ts.ModuleBody = type.symbol.valueDeclaration.parent;
|
|
|
|
while(moduleBody) {
|
|
let moduleDeclaration:ts.ModuleDeclaration = moduleBody.parent;
|
|
let nameOfModule:string = undefined;
|
|
while(moduleDeclaration) {
|
|
let ns = moduleDeclaration.name.text;
|
|
ns = ns.startsWith("$") ? ns.substring(1) : ns;
|
|
nameOfModule = nameOfModule ? (ns + '/' + nameOfModule) : ns;
|
|
if (ts.isModuleDeclaration(moduleDeclaration.parent)) {
|
|
moduleDeclaration = moduleDeclaration.parent;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
ret.push(nameOfModule);
|
|
if (moduleDeclaration && ts.isModuleBlock(moduleDeclaration.parent)) {
|
|
moduleBody = moduleDeclaration.parent;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return ret.reverse();
|
|
}
|
|
}
|
|
}
|
|
|
|
//function getOwnEmitOutputFilePath(fileName: string) {
|
|
function getModulePath(fileName: string) {
|
|
const compilerOptions = options;
|
|
let emitOutputFilePathWithoutExtension: string;
|
|
if (compilerOptions.outDir) {
|
|
emitOutputFilePathWithoutExtension = tsi.removeFileExtension(tsi.getSourceFilePathInNewDir(fileName, customSystem.getCurrentDirectory(), (program as any).getCommonSourceDirectory(), options.outDir));
|
|
}
|
|
else {
|
|
emitOutputFilePathWithoutExtension = tsi.removeFileExtension(fileName);
|
|
}
|
|
|
|
return emitOutputFilePathWithoutExtension;
|
|
}
|
|
|
|
function list(pattern: string):void {
|
|
var re = new RegExp(pattern ? pattern : '.*');
|
|
console.log(`id\t\t\t\t\t\t\t\t\tprocessed\tisBP\tpath`);
|
|
for(var key in fileVersions) {
|
|
var value = fileVersions[key];
|
|
if (!pattern || re.test(key)) {
|
|
console.log(`${value.version}\t${!!value.processed}\t\t${!!value.isBP}\t${key}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function compile(id: string): void {
|
|
for(var key in fileVersions) {
|
|
var value = fileVersions[key];
|
|
if (value.version === id.trim()) {
|
|
console.log(`compiling ${key} ...`);
|
|
onSourceFileAddOrChange(key, true);
|
|
}
|
|
}
|
|
refreshBlueprints();
|
|
}
|
|
|
|
function dispatchCmd(cmd:string, args:string) {
|
|
if (cmd == 'ls') {
|
|
list(args);
|
|
} else if (cmd == 'compile') {
|
|
compile(args);
|
|
} else {
|
|
console.error(`unknow command for Puerts ${cmd}`);
|
|
}
|
|
}
|
|
|
|
cpp.FPuertsEditorModule.SetCmdCallback(dispatchCmd);
|
|
}
|
|
|
|
watch(customSystem.getCurrentDirectory() + "tsconfig.json");
|