Class: Pindo::Unity::UnityProcHelper

Inherits:
Object
  • Object
show all
Defined in:
lib/pindo/module/unity/unity_proc_helper.rb

Overview

Unity 进程管理助手负责 Unity 进程的检测、管理和清理所有方法均为类方法,无需实例化

Class Method Summary collapse

Class Method Details

.check_unity_processes(unity_exe_full_path: nil, project_path: nil) ⇒ Object

构建前检查 Unity 进程(交互式)

Parameters:

  • unity_exe_full_path (String) (defaults to: nil)

    Unity 可执行文件路径

  • project_path (String) (defaults to: nil)

    Unity 项目路径



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/pindo/module/unity/unity_proc_helper.rb', line 11

def self.check_unity_processes(unity_exe_full_path: nil, project_path: nil)
  # 如果没有提供路径参数,直接跳过Unity进程检查
  if unity_exe_full_path.nil? && project_path.nil?
    return
  end

  # 检查是否有Unity进程在运行
  unity_processes = `ps aux | grep -i unity | grep -v grep`.strip

  if !unity_processes.empty?
    # 解析Unity进程信息,传入Unity路径和项目路径进行精确匹配
    unity_pids = parse_unity_processes(unity_processes, unity_exe_full_path: unity_exe_full_path, project_path: project_path)

    # 过滤掉僵尸进程和无效进程
    unity_pids = filter_valid_unity_processes(unity_pids)

    if unity_pids.any?
      puts "⚠️  检测到与当前项目相关的 Unity 进程正在运行:"
      # 只显示真正的Unity进程,不显示 pindo 相关进程
      unity_pids.each_with_index do |process_info, index|
        puts "  #{index + 1}. PID: #{process_info[:pid]}, 命令: #{process_info[:command]}"
      end
      puts ""

      # 询问用户是否要自动关闭Unity进程
      loop do
        puts "批处理模式需要关闭这些 Unity 进程以避免冲突"
        puts "  [y] 是, 自动关闭所有 Unity 进程"
        puts "  [s] 跳过, 我已经手动关闭 Unity 进程,继续构建(可能导致冲突)"
        puts "  [e] 退出编译"
        print "请输入选择 (y/s/e): "

        input = STDIN.gets
        choice = input ? input.chomp.strip.downcase : ""

        case choice
        when 'y', 'yes', '1'
          puts "\n正在关闭 Unity 相关进程..."
          success_count = 0
          unity_pids.each do |process_info|
            if kill_unity_process(process_info[:pid])
              puts "✅ 成功关闭进程 #{process_info[:pid]}"
              success_count += 1
            else
              puts "❌ 关闭进程 #{process_info[:pid]} 失败"
            end
          end

          if success_count > 0
            puts "\n✅ 已关闭 #{success_count} 个 Unity 相关进程"
            puts "等待3秒后继续编译..."
            sleep(3)
          else
            puts "\n❌ 无法关闭 Unity 相关进程,请手动关闭后重试"
            raise "Unity进程冲突:无法自动关闭 Unity 相关进程,请手动关闭后重试"
          end
          break  # 退出循环

        when 's', 'skip', '2'
          puts "\n✅ 跳过检查,继续编译..."
          puts "假设Unity Editor已经手动关闭,如果仍在运行可能导致编译失败"
          sleep(1)
          break  # 退出循环

        when 'e', 'exit', '3'
          puts "\n⚠️  用户选择退出编译"
          puts "退出Unity编译流程"
          exit 0

        else
          puts "\n⚠️ 无效选择: '#{choice}'"
          puts "请输入 y (是), s (跳过), 或 e (退出)\n\n"
          # 继续循环,让用户重新输入
        end
      end
    else
      # 没有检测到与当前项目相关的Unity进程,静默继续
    end
  end
end

.cleanup_unity_processes_after_build(unity_exe_full_path: nil, project_path: nil) ⇒ Object

构建完成后清理 Unity 进程残留(自动)

Parameters:

  • unity_exe_full_path (String) (defaults to: nil)

    Unity 可执行文件路径

  • project_path (String) (defaults to: nil)

    Unity 项目路径



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/pindo/module/unity/unity_proc_helper.rb', line 95

def self.cleanup_unity_processes_after_build(unity_exe_full_path: nil, project_path: nil)
  begin
    # 等待一小段时间让 Unity 进程自然退出
    sleep(2)

    # 如果没有提供路径参数,不清理进程
    if unity_exe_full_path.nil? && project_path.nil?
      return
    end

    # 检查是否还有 Unity 进程在运行
    unity_processes = `ps aux | grep -i unity | grep -v grep`.strip

    if !unity_processes.empty?
      # 解析进程信息,传入Unity路径和项目路径进行精确匹配
      unity_pids = parse_unity_processes(unity_processes, unity_exe_full_path: unity_exe_full_path, project_path: project_path)

      if unity_pids.any?
        puts "\e[33m检测到构建后仍有项目相关的 Unity 进程残留,正在清理...\e[0m"
        # 自动清理残留的 Unity 进程
        cleaned_count = 0
        unity_pids.each do |process_info|
          if kill_unity_process(process_info[:pid])
            cleaned_count += 1
          end
        end

        if cleaned_count > 0
          puts "\e[32m✅ 已清理 #{cleaned_count} 个 Unity 相关进程残留\e[0m"
        end
      end
    end
  rescue => e
    # 清理失败不影响主流程
    # 静默处理,不输出错误信息
  end
end

.filter_valid_unity_processes(processes) ⇒ Array<Hash>

过滤掉僵尸进程和无效进程

Parameters:

  • processes (Array<Hash>)

    进程信息数组

Returns:

  • (Array<Hash>)

    有效进程数组



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/pindo/module/unity/unity_proc_helper.rb', line 202

def self.filter_valid_unity_processes(processes)
  valid_processes = []

  processes.each do |process_info|
    # PID 可能是字符串,确保正确处理
    pid = process_info[:pid]
    pid_int = pid.to_i

    # 检查进程是否真的存在且活跃
    if process_exists_and_active?(pid_int)
      valid_processes << process_info
    end
  end

  valid_processes
end

.get_process_info(pid) ⇒ String?

获取进程信息

Parameters:

  • pid (Integer)

    进程 ID

Returns:

  • (String, nil)

    进程命令名称



365
366
367
368
369
370
371
# File 'lib/pindo/module/unity/unity_proc_helper.rb', line 365

def self.get_process_info(pid)
  begin
    `ps -p #{pid} -o comm=`.strip
  rescue => e
    nil
  end
end

.kill_unity_process(pid) ⇒ Boolean

关闭 Unity 进程

Parameters:

  • pid (String, Integer)

    进程 ID

Returns:

  • (Boolean)

    true 如果成功关闭



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/pindo/module/unity/unity_proc_helper.rb', line 265

def self.kill_unity_process(pid)
  begin
    pid_int = pid.to_i

    # 安全检查:确保不是当前进程
    if pid_int == Process.pid
      puts "⚠️  跳过当前进程 #{pid}"
      return true
    end

    # 检查进程是否存在
    unless process_exists?(pid_int)
      puts "进程 #{pid} 已不存在"
      return true
    end

    # 获取进程信息进行额外验证
    process_info = get_process_info(pid_int)
    if process_info && !process_info.include?('Unity')
      puts "⚠️  跳过非Unity进程 #{pid}: #{process_info}"
      return true
    end

    puts "正在关闭Unity进程 #{pid}..."

    # 先尝试优雅关闭
    begin
      Process.kill('TERM', pid_int)
      puts "已发送TERM信号给进程 #{pid}"
    rescue Errno::EPERM
      puts "❌ 没有权限关闭进程 #{pid},尝试使用sudo"
      return kill_unity_process_with_sudo(pid)
    rescue Errno::ESRCH
      puts "进程 #{pid} 已不存在"
      return true
    end

    # 等待进程优雅退出
    sleep(3)

    # 检查进程是否还存在
    if process_exists?(pid_int)
      puts "进程 #{pid} 未响应TERM信号,尝试强制关闭..."
      begin
        Process.kill('KILL', pid_int)
        puts "已发送KILL信号给进程 #{pid}"
        sleep(2)
      rescue Errno::EPERM
        puts "❌ 没有权限强制关闭进程 #{pid}"
        return false
      rescue Errno::ESRCH
        puts "进程 #{pid} 已不存在"
        return true
      end
    end

    # 最终检查
    if process_exists?(pid_int)
      puts "❌ 无法关闭进程 #{pid}"
      false
    else
      puts "✅ 成功关闭进程 #{pid}"
      true
    end

  rescue => e
    puts "❌ 关闭进程 #{pid} 时出错: #{e.message}"
    puts "错误类型: #{e.class}"
    false
  end
end

.kill_unity_process_with_sudo(pid) ⇒ Boolean

使用 sudo 关闭进程

Parameters:

  • pid (String, Integer)

    进程 ID

Returns:

  • (Boolean)

    true 如果成功关闭



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/pindo/module/unity/unity_proc_helper.rb', line 340

def self.kill_unity_process_with_sudo(pid)
  begin
    puts "尝试使用sudo关闭进程 #{pid}..."
    result = system("sudo kill -TERM #{pid}")
    if result
      sleep(3)
      if process_exists?(pid.to_i)
        puts "TERM信号无效,尝试KILL信号..."
        system("sudo kill -KILL #{pid}")
        sleep(2)
      end
      !process_exists?(pid.to_i)
    else
      puts "❌ sudo命令执行失败"
      false
    end
  rescue => e
    puts "❌ sudo关闭进程时出错: #{e.message}"
    false
  end
end

.parse_unity_processes(processes_output, unity_exe_full_path: nil, project_path: nil) ⇒ Array<Hash>

解析 Unity 进程信息

Parameters:

  • processes_output (String)

    ps 命令输出

  • unity_exe_full_path (String) (defaults to: nil)

    Unity 可执行文件路径

  • project_path (String) (defaults to: nil)

    Unity 项目路径

Returns:

  • (Array<Hash>)

    进程信息数组



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/pindo/module/unity/unity_proc_helper.rb', line 138

def self.parse_unity_processes(processes_output, unity_exe_full_path: nil, project_path: nil)
  processes = []

  processes_output.lines.each do |line|
    # 解析 ps aux 输出格式
    parts = line.strip.split(/\s+/)
    if parts.length >= 11
      pid = parts[1]
      command = parts[10..-1].join(' ')

      # 过滤掉grep进程本身和pindo进程
      unless command.include?('grep') || command.include?('pindo')
        # 精确匹配Unity进程
        is_relevant_unity_process = false
        match_reasons = []

        # 1. 必须是Unity Editor主进程(排除VS Code等其他进程)
        is_unity_editor = command.match?(/Unity\.app.*\/Unity/i) || command.match?(/Unity\.exe/i)

        if is_unity_editor
          # 2. 检查是否是打开了指定项目的Unity进程
          # Unity 使用 -projectpath 参数指定项目路径
          if project_path
            # 标准化路径以确保匹配
            normalized_project_path = File.expand_path(project_path)
            # 精确匹配项目路径(必须完全匹配,不能只是包含)
            if command.match?(/-projectpath\s+#{Regexp.escape(normalized_project_path)}(\s|$)/i)
              # 只有当项目路径完全匹配时才认为是相关进程
              is_relevant_unity_process = true
              match_reasons << "Unity Editor打开了当前项目"

              # 如果还指定了Unity路径,也要验证
              if unity_exe_full_path
                if command.include?(unity_exe_full_path)
                  match_reasons << "使用指定的Unity版本"
                else
                  # Unity路径不匹配,不是我们要的进程
                  is_relevant_unity_process = false
                  match_reasons.clear
                end
              end
            end
          elsif unity_exe_full_path && command.include?(unity_exe_full_path)
            # 只提供了Unity路径,没有项目路径时才匹配
            # 这种情况下匹配所有使用该Unity版本的进程
            is_relevant_unity_process = true
            match_reasons << "使用指定的Unity版本"
          end
        end

        # 3. 如果没有提供路径参数,不匹配任何进程(由上层函数处理)

        if is_relevant_unity_process
          processes << { pid: pid, command: command }
        end
      end
    end
  end
  processes
end

.process_exists?(pid) ⇒ Boolean

检查进程是否存在

Parameters:

  • pid (Integer)

    进程 ID

Returns:

  • (Boolean)

    true 如果进程存在



376
377
378
379
380
381
382
383
384
385
386
# File 'lib/pindo/module/unity/unity_proc_helper.rb', line 376

def self.process_exists?(pid)
  begin
    Process.kill(0, pid)
    true
  rescue Errno::ESRCH
    false
  rescue Errno::EPERM
    # 权限不足,但进程存在
    true
  end
end

.process_exists_and_active?(pid) ⇒ Boolean

检查进程是否真的存在且活跃

Parameters:

  • pid (Integer)

    进程 ID

Returns:

  • (Boolean)

    true 如果进程存在且活跃



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/pindo/module/unity/unity_proc_helper.rb', line 222

def self.process_exists_and_active?(pid)
  begin
    # 使用更简单可靠的方式检查进程
    # 尝试发送信号0来检查进程是否存在
    Process.kill(0, pid)

    # 如果需要检查进程状态,使用不同的命令格式
    # -o pid=,stat= 去掉标题行
    process_info = `ps -p #{pid} -o pid=,stat= 2>/dev/null`.strip

    if process_info.empty?
      return false
    end

    # 解析进程状态
    parts = process_info.split(/\s+/)
    if parts.length >= 2
      stat = parts[1]
      # Z = 僵尸进程, T = 停止进程
      # 只过滤僵尸进程,不过滤停止进程(T可能是正常的暂停状态)
      if stat.include?('Z')
        puts "        进程 #{pid} 是僵尸进程,过滤掉"
        return false
      end
    end

    true
  rescue Errno::ESRCH
    # 进程不存在
    false
  rescue Errno::EPERM
    # 权限不足,但进程存在
    true
  rescue => e
    puts "检查进程 #{pid} 时出错: #{e.message}"
    # 如果出错,假设进程存在(保守处理)
    true
  end
end