mm_machine 2.8.3 → 2.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/drive.js +25 -0
  2. package/index.js +151 -30
  3. package/package.json +1 -1
package/drive.js CHANGED
@@ -135,9 +135,34 @@ Drive.prototype.setConfig = function (config) {
135
135
  }
136
136
  }
137
137
  this._preset();
138
+ // 通知父级刷新快照
139
+ this._notifySnapshot();
138
140
  return this.config;
139
141
  };
140
142
 
143
+ /**
144
+ * 通知父级Manager刷新快照(防抖)
145
+ */
146
+ Drive.prototype._notifySnapshot = function () {
147
+ if (!this.getParent) {
148
+ return;
149
+ }
150
+ var parent = this.getParent();
151
+ if (!parent || !parent.config || !parent.config.use_snapshot) {
152
+ return;
153
+ }
154
+ // 防抖:避免频繁写文件,500ms内只刷新一次
155
+ if (this._snapshot_timer) {
156
+ return;
157
+ }
158
+ this._snapshot_timer = setTimeout(() => {
159
+ this._snapshot_timer = null;
160
+ if (parent.refreshSnapshot) {
161
+ parent.refreshSnapshot();
162
+ }
163
+ }, 500);
164
+ };
165
+
141
166
  /**
142
167
  * 获取配置
143
168
  * @returns {object} 配置项
package/index.js CHANGED
@@ -489,13 +489,13 @@ Manager.prototype.getSnapshotPath = function () {
489
489
 
490
490
  /**
491
491
  * 保存快照数据
492
- * @param {object} files_map 目录到文件列表的映射 { dir: [file_path, ...] }
492
+ * @param {object} mods_map 目录到模块条目的映射 { dir: [{ file, config }, ...] }
493
493
  */
494
- Manager.prototype._saveSnapshot = function (files_map) {
494
+ Manager.prototype._saveSnapshot = function (mods_map) {
495
495
  try {
496
496
  var snapshot_path = this.getSnapshotPath();
497
497
  var snapshot = {
498
- files_map: files_map,
498
+ mods_map: mods_map,
499
499
  updated_at: Date.now()
500
500
  };
501
501
  snapshot_path.saveJson(snapshot);
@@ -504,6 +504,47 @@ Manager.prototype._saveSnapshot = function (files_map) {
504
504
  }
505
505
  };
506
506
 
507
+ /**
508
+ * 从当前已加载模块刷新快照
509
+ * 确保快照与内存中模块状态一致
510
+ */
511
+ Manager.prototype.refreshSnapshot = function () {
512
+ if (!this.config.use_snapshot) {
513
+ return;
514
+ }
515
+ var mods_map = {};
516
+ for (var name in this.mods) {
517
+ var mod = this.mods[name];
518
+ var f = mod.config_file;
519
+ if (!f) {
520
+ continue;
521
+ }
522
+ var dir = this._findSnapshotDir(f);
523
+ if (!mods_map[dir]) {
524
+ mods_map[dir] = [];
525
+ }
526
+ mods_map[dir].push({ file: f, config: mod.config });
527
+ }
528
+ this._saveSnapshot(mods_map);
529
+ };
530
+
531
+ /**
532
+ * 推断配置文件所属的快照检索目录
533
+ * @param {string} file 配置文件路径
534
+ * @returns {string} 检索目录
535
+ */
536
+ Manager.prototype._findSnapshotDir = function (file) {
537
+ var base_dir = this.getBaseDir();
538
+ if (base_dir && file.startsWith(base_dir)) {
539
+ return base_dir;
540
+ }
541
+ var dir = this.getDir();
542
+ if (dir && file.startsWith(dir)) {
543
+ return dir;
544
+ }
545
+ return file.dirname();
546
+ };
547
+
507
548
  /**
508
549
  * 加载快照数据
509
550
  * @returns {object|null} 快照数据,不存在时返回null
@@ -515,7 +556,7 @@ Manager.prototype._loadSnapshot = function () {
515
556
  return null;
516
557
  }
517
558
  var snapshot = snapshot_path.loadJson();
518
- if (!snapshot || !snapshot.files_map) {
559
+ if (!snapshot || !snapshot.mods_map) {
519
560
  return null;
520
561
  }
521
562
  return snapshot;
@@ -526,31 +567,68 @@ Manager.prototype._loadSnapshot = function () {
526
567
  };
527
568
 
528
569
  /**
529
- * 从快照快速加载模块,不进行文件系统扫描
530
- * @param {object} files_map 目录到文件列表的映射
570
+ * 从快照快速加载模块,不进行文件系统扫描和磁盘配置读取
571
+ * 使用快照中的config直接创建模块,跳过loadConfig的IO
572
+ * @param {object} mods_map 目录到模块条目的映射 { dir: [{ file, config }, ...] }
531
573
  * @param {boolean} clear 是否清除已有模块
532
574
  */
533
- Manager.prototype._loadFromSnapshot = function (files_map, clear = true) {
575
+ Manager.prototype._loadFromSnapshot = function (mods_map, clear = true) {
534
576
  if (clear) {
535
577
  this.clearMods();
536
578
  }
537
- for (var dir in files_map) {
538
- var files = files_map[dir];
539
- if (files && files.length > 0) {
540
- this.registerMods(files);
579
+ for (var dir in mods_map) {
580
+ var entries = mods_map[dir];
581
+ if (!entries || entries.length === 0) {
582
+ continue;
583
+ }
584
+ for (var i = 0; i < entries.length; i++) {
585
+ var entry = entries[i];
586
+ var f = entry.file;
587
+ // 跳过已不存在的文件,等待后台同步处理
588
+ if (!f.hasFile || !f.hasFile()) {
589
+ continue;
590
+ }
591
+ this._newModFromSnap(f, entry.config);
541
592
  }
542
593
  }
543
594
  this.updateInfo();
544
595
  this.is_loaded = true;
545
596
  };
546
597
 
598
+ /**
599
+ * 用快照config创建模块,跳过磁盘读取
600
+ * @param {string} file 配置文件路径
601
+ * @param {object} config 模块配置
602
+ * @returns {object} 模块对象
603
+ */
604
+ Manager.prototype._newModFromSnap = function (file, config) {
605
+ var mod;
606
+ if (this.config.mod_type === 'script') {
607
+ mod = new Mod(config || {}, this);
608
+ } else {
609
+ mod = new this.Drive(config || {}, this);
610
+ }
611
+ mod.mode = this.config.mode;
612
+ mod.config_file = file;
613
+ if (mod._preset) {
614
+ mod._preset();
615
+ }
616
+ return this.setMod(mod.config.name, mod);
617
+ };
618
+
547
619
  /**
548
620
  * 后台增量同步:扫描文件系统并与快照进行diff,增减模块
621
+ * 使用锁防止多次重入冲突
549
622
  * @param {object} snapshot 快照数据
550
623
  */
551
624
  Manager.prototype._syncFromFile = async function (snapshot) {
625
+ // 防重入锁,避免多次同步同时执行
626
+ if (this._syncing) {
627
+ return;
628
+ }
629
+ this._syncing = true;
552
630
  try {
553
- var old_dirs = snapshot.files_map || {};
631
+ var old_dirs = snapshot.mods_map || {};
554
632
  var old = this._buildSet(old_dirs);
555
633
 
556
634
  // 扫描目录并注册新模块
@@ -567,25 +645,28 @@ Manager.prototype._syncFromFile = async function (snapshot) {
567
645
  }
568
646
 
569
647
  // 移除已删除的模块
570
- this._removeStale(old);
648
+ await this._removeStale(old);
571
649
  this.updateInfo();
572
650
  await this._saveSnapshotDirs(Object.keys(old_dirs));
651
+ this.log('info', '快照同步完成');
573
652
  } catch (err) {
574
653
  this.log('error', '快照增量同步失败', err);
654
+ } finally {
655
+ this._syncing = false;
575
656
  }
576
657
  };
577
658
 
578
659
  /**
579
660
  * 构建文件集合
580
- * @param {object} dirs 目录到文件列表的映射
661
+ * @param {object} dirs 目录到模块条目的映射 { dir: [{ file, config }, ...] }
581
662
  * @returns {object} 文件集合 { file: true }
582
663
  */
583
664
  Manager.prototype._buildSet = function (dirs) {
584
665
  var set = {};
585
666
  for (var dir in dirs) {
586
- var files = dirs[dir];
587
- for (var i = 0; i < files.length; i++) {
588
- set[files[i]] = true;
667
+ var entries = dirs[dir];
668
+ for (var i = 0; i < entries.length; i++) {
669
+ set[entries[i].file] = true;
589
670
  }
590
671
  }
591
672
  return set;
@@ -595,12 +676,12 @@ Manager.prototype._buildSet = function (dirs) {
595
676
  * 移除已删除的模块
596
677
  * @param {object} old 旧文件集合,标记为false表示仍存在
597
678
  */
598
- Manager.prototype._removeStale = function (old) {
679
+ Manager.prototype._removeStale = async function (old) {
599
680
  for (var file in old) {
600
681
  if (old[file] === true) {
601
682
  var mod = this.getMod(file);
602
683
  if (mod) {
603
- this.unloadMod(mod.config.name);
684
+ await this.unloadMod(mod.config.name);
604
685
  this.delMod(mod.config.name);
605
686
  this.log('info', '快照同步: 移除模块 ' + file);
606
687
  }
@@ -609,16 +690,52 @@ Manager.prototype._removeStale = function (old) {
609
690
  };
610
691
 
611
692
  /**
612
- * 保存快照目录
693
+ * 构建文件到config的映射
694
+ * @returns {object} 文件到config的映射 { file_path: config }
695
+ */
696
+ Manager.prototype._buildConfigMap = function () {
697
+ var map = {};
698
+ var mods = this.getMods();
699
+ for (var name in mods) {
700
+ var mod = mods[name];
701
+ if (mod.config_file) {
702
+ map[mod.config_file] = mod.config;
703
+ }
704
+ }
705
+ return map;
706
+ };
707
+
708
+ /**
709
+ * 构建模块条目列表
710
+ * @param {string[]} files 文件路径数组
711
+ * @param {object} config_dict 文件到config的映射
712
+ * @returns {object[]} 模块条目数组 [{ file, config }]
713
+ */
714
+ Manager.prototype._buildEntries = function (files, config_dict) {
715
+ var entries = [];
716
+ for (var i = 0; i < files.length; i++) {
717
+ var f = files[i];
718
+ var config = config_dict[f];
719
+ if (config) {
720
+ entries.push({ file: f, config: config });
721
+ }
722
+ }
723
+ return entries;
724
+ };
725
+
726
+ /**
727
+ * 保存快照目录(含config)
613
728
  * @param {string[]} dirs 目录列表
614
729
  */
615
730
  Manager.prototype._saveSnapshotDirs = async function (dirs) {
616
- var new_dirs = {};
731
+ var new_map = {};
732
+ var config_dict = this._buildConfigMap();
617
733
  for (var i = 0; i < dirs.length; i++) {
618
734
  var dir = dirs[i];
619
- new_dirs[dir] = await this._findFiles(dir);
735
+ var files = await this._findFiles(dir);
736
+ new_map[dir] = this._buildEntries(files, config_dict);
620
737
  }
621
- this._saveSnapshot(new_dirs);
738
+ this._saveSnapshot(new_map);
622
739
  };
623
740
 
624
741
  /**
@@ -786,8 +903,8 @@ Manager.prototype._loadSources = async function () {
786
903
  if (this.config.use_snapshot) {
787
904
  var snapshot = this._loadSnapshot();
788
905
  if (snapshot) {
789
- // 快照存在,快速加载模块
790
- this._loadFromSnapshot(snapshot.files_map);
906
+ // 快照存在,用config快速加载模块
907
+ this._loadFromSnapshot(snapshot.mods_map);
791
908
  this.log('info', '已从快照快速加载模块,正在后台同步文件变更...');
792
909
  // 异步增量同步,不阻塞启动
793
910
  this._syncFromFile(snapshot);
@@ -803,14 +920,16 @@ Manager.prototype._loadSources = async function () {
803
920
  await this.call('update', dir, true);
804
921
  }
805
922
 
806
- // 首次扫描后保存快照
923
+ // 首次扫描后保存快照(含config)
807
924
  if (this.config.use_snapshot) {
808
- var files_map = {};
925
+ var dirs = [];
809
926
  if (base_dir) {
810
- files_map[base_dir] = await this._findFiles(base_dir);
927
+ dirs.push(base_dir);
928
+ }
929
+ if (dir) {
930
+ dirs.push(dir);
811
931
  }
812
- files_map[dir] = await this._findFiles(dir);
813
- this._saveSnapshot(files_map);
932
+ await this._saveSnapshotDirs(dirs);
814
933
  }
815
934
  };
816
935
 
@@ -1148,6 +1267,7 @@ Manager.prototype.createFile = function (config, file) {
1148
1267
  Manager.prototype.create = function (config, file = '') {
1149
1268
  let f = this.createFile(config, file);
1150
1269
  let mod = this.registerMod(f, config);
1270
+ this.refreshSnapshot();
1151
1271
  return mod;
1152
1272
  };
1153
1273
 
@@ -1162,6 +1282,7 @@ Manager.prototype.remove = async function (name) {
1162
1282
  await mod.remove();
1163
1283
  }
1164
1284
  this.unregisterMod(name);
1285
+ this.refreshSnapshot();
1165
1286
  return mod;
1166
1287
  };
1167
1288
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mm_machine",
3
- "version": "2.8.3",
3
+ "version": "2.8.4",
4
4
  "description": "A flexible Node.js plugin mechanism system for dynamic loading, management and execution of modules. Supports hot reload, lifecycle management, and modern JavaScript features.",
5
5
  "main": "index.js",
6
6
  "scripts": {